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 Deserialize,
79 Serialize,
80 Serializer,
81 de,
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!("{b:x?}"))
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}
340
341impl Display for Language {
342 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
343 match self {
344 Self::Ink => write!(f, "ink!"),
345 Self::Solidity => write!(f, "Solidity"),
346 }
347 }
348}
349
350impl FromStr for Language {
351 type Err = String;
352
353 fn from_str(s: &str) -> Result<Self, Self::Err> {
354 match s {
355 "ink!" => Ok(Self::Ink),
356 "Solidity" => Ok(Self::Solidity),
357 _ => Err(format!("Invalid language '{s}'")),
358 }
359 }
360}
361
362#[derive(Clone, Debug)]
364pub struct SourceCompiler {
365 pub compiler: Compiler,
367 pub version: Version,
369}
370
371impl Display for SourceCompiler {
372 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
373 write!(f, "{} {}", self.compiler, self.version)
374 }
375}
376
377impl FromStr for SourceCompiler {
378 type Err = String;
379
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 let mut parts = s.split_whitespace();
382
383 let compiler = parts
384 .next()
385 .ok_or_else(|| {
386 format!(
387 "SourceCompiler: Expected format '<compiler> <version>', got '{s}'"
388 )
389 })
390 .and_then(FromStr::from_str)?;
391
392 let version = parts
393 .next()
394 .ok_or_else(|| {
395 format!(
396 "SourceCompiler: Expected format '<compiler> <version>', got '{s}'"
397 )
398 })
399 .and_then(|v| {
400 <Version as FromStr>::from_str(v)
401 .map_err(|e| format!("Error parsing version {e}"))
402 })?;
403
404 Ok(Self { compiler, version })
405 }
406}
407
408impl Serialize for SourceCompiler {
409 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
410 where
411 S: Serializer,
412 {
413 serializer.serialize_str(&self.to_string())
414 }
415}
416
417impl<'de> Deserialize<'de> for SourceCompiler {
418 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
419 where
420 D: de::Deserializer<'de>,
421 {
422 let s = String::deserialize(deserializer)?;
423 FromStr::from_str(&s).map_err(de::Error::custom)
424 }
425}
426
427impl SourceCompiler {
428 pub fn new(compiler: Compiler, version: Version) -> Self {
429 SourceCompiler { compiler, version }
430 }
431}
432
433#[derive(Clone, Debug, Deserialize, Serialize)]
435pub enum Compiler {
436 RustC,
438 Solang,
441}
442
443impl Display for Compiler {
444 fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult {
445 match self {
446 Self::RustC => write!(f, "rustc"),
447 Self::Solang => write!(f, "solang"),
448 }
449 }
450}
451
452impl FromStr for Compiler {
453 type Err = String;
454
455 fn from_str(s: &str) -> Result<Self, Self::Err> {
456 match s {
457 "rustc" => Ok(Self::RustC),
458 "solang" => Ok(Self::Solang),
459 _ => Err(format!("Invalid compiler '{s}'")),
460 }
461 }
462}
463
464#[derive(Clone, Debug, Deserialize, Serialize)]
466pub struct Contract {
467 pub name: String,
469 pub version: Version,
471 pub authors: Vec<String>,
473 #[serde(skip_serializing_if = "Option::is_none")]
475 pub description: Option<String>,
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub documentation: Option<Url>,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub repository: Option<Url>,
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub homepage: Option<Url>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub license: Option<String>,
488}
489
490impl Contract {
491 pub fn builder() -> ContractBuilder {
492 ContractBuilder::default()
493 }
494}
495
496#[derive(Clone, Debug, Deserialize, Serialize)]
498pub struct User {
499 #[serde(flatten)]
501 pub json: Map<String, Value>,
502}
503
504impl User {
505 pub fn new(json: Map<String, Value>) -> Self {
507 User { json }
508 }
509}
510
511#[derive(Default)]
513pub struct ContractBuilder {
514 name: Option<String>,
515 version: Option<Version>,
516 authors: Option<Vec<String>>,
517 description: Option<String>,
518 documentation: Option<Url>,
519 repository: Option<Url>,
520 homepage: Option<Url>,
521 license: Option<String>,
522}
523
524impl ContractBuilder {
525 pub fn name<S>(&mut self, name: S) -> &mut Self
527 where
528 S: AsRef<str>,
529 {
530 if self.name.is_some() {
531 panic!("name has already been set")
532 }
533 self.name = Some(name.as_ref().to_string());
534 self
535 }
536
537 pub fn version(&mut self, version: Version) -> &mut Self {
539 if self.version.is_some() {
540 panic!("version has already been set")
541 }
542 self.version = Some(version);
543 self
544 }
545
546 pub fn authors<I, S>(&mut self, authors: I) -> &mut Self
548 where
549 I: IntoIterator<Item = S>,
550 S: AsRef<str>,
551 {
552 if self.authors.is_some() {
553 panic!("authors has already been set")
554 }
555
556 let authors = authors
557 .into_iter()
558 .map(|s| s.as_ref().to_string())
559 .collect::<Vec<_>>();
560
561 if authors.is_empty() {
562 panic!("must have at least one author")
563 }
564
565 self.authors = Some(authors);
566 self
567 }
568
569 pub fn description<S>(&mut self, description: S) -> &mut Self
571 where
572 S: AsRef<str>,
573 {
574 if self.description.is_some() {
575 panic!("description has already been set")
576 }
577 self.description = Some(description.as_ref().to_string());
578 self
579 }
580
581 pub fn documentation(&mut self, documentation: Url) -> &mut Self {
583 if self.documentation.is_some() {
584 panic!("documentation is already set")
585 }
586 self.documentation = Some(documentation);
587 self
588 }
589
590 pub fn repository(&mut self, repository: Url) -> &mut Self {
592 if self.repository.is_some() {
593 panic!("repository is already set")
594 }
595 self.repository = Some(repository);
596 self
597 }
598
599 pub fn homepage(&mut self, homepage: Url) -> &mut Self {
601 if self.homepage.is_some() {
602 panic!("homepage is already set")
603 }
604 self.homepage = Some(homepage);
605 self
606 }
607
608 pub fn license<S>(&mut self, license: S) -> &mut Self
610 where
611 S: AsRef<str>,
612 {
613 if self.license.is_some() {
614 panic!("license has already been set")
615 }
616 self.license = Some(license.as_ref().to_string());
617 self
618 }
619
620 pub fn build(&self) -> Result<Contract, String> {
624 let mut required = Vec::new();
625
626 if let (Some(name), Some(version), Some(authors)) =
627 (&self.name, &self.version, &self.authors)
628 {
629 Ok(Contract {
630 name: name.to_string(),
631 version: version.clone(),
632 authors: authors.to_vec(),
633 description: self.description.clone(),
634 documentation: self.documentation.clone(),
635 repository: self.repository.clone(),
636 homepage: self.homepage.clone(),
637 license: self.license.clone(),
638 })
639 } else {
640 if self.name.is_none() {
641 required.push("name");
642 }
643 if self.version.is_none() {
644 required.push("version")
645 }
646 if self.authors.is_none() {
647 required.push("authors")
648 }
649 Err(format!(
650 "Missing required non-default fields: {}",
651 required.join(", ")
652 ))
653 }
654 }
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660 use pretty_assertions::assert_eq;
661 use serde_json::json;
662
663 #[test]
664 fn builder_fails_with_missing_required_fields() {
665 let missing_name = Contract::builder()
666 .version(Version::new(2, 1, 0))
668 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
669 .build();
670
671 assert_eq!(
672 missing_name.unwrap_err(),
673 "Missing required non-default fields: name"
674 );
675
676 let missing_version = Contract::builder()
677 .name("incrementer")
678 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
680 .build();
681
682 assert_eq!(
683 missing_version.unwrap_err(),
684 "Missing required non-default fields: version"
685 );
686
687 let missing_authors = Contract::builder()
688 .name("incrementer")
689 .version(Version::new(2, 1, 0))
690 .build();
692
693 assert_eq!(
694 missing_authors.unwrap_err(),
695 "Missing required non-default fields: authors"
696 );
697
698 let missing_all = Contract::builder()
699 .build();
703
704 assert_eq!(
705 missing_all.unwrap_err(),
706 "Missing required non-default fields: name, version, authors"
707 );
708 }
709
710 #[test]
711 fn json_with_optional_fields() {
712 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
713 let compiler = SourceCompiler::new(
714 Compiler::RustC,
715 Version::parse("1.46.0-nightly").unwrap(),
716 );
717 let contract_binary = SourceContractBinary::new(vec![0u8, 1u8, 2u8]);
718 let build_info = json! {
719 {
720 "example_compiler_version": 42,
721 "example_settings": [],
722 "example_name": "increment"
723 }
724 }
725 .as_object()
726 .unwrap()
727 .clone();
728
729 let source = Source::new(
730 Some(contract_binary),
731 CodeHash([0u8; 32]),
732 language,
733 compiler,
734 Some(build_info),
735 );
736
737 let contract = Contract::builder()
738 .name("incrementer")
739 .version(Version::new(2, 1, 0))
740 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
741 .description("increment a value")
742 .documentation(Url::parse("http://docs.rs/").unwrap())
743 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
744 .homepage(Url::parse("http://example.com/").unwrap())
745 .license("Apache-2.0")
746 .build()
747 .unwrap();
748
749 let user_json = json! {
750 {
751 "more-user-provided-fields": [
752 "and",
753 "their",
754 "values"
755 ],
756 "some-user-provided-field": "and-its-value"
757 }
758 };
759 let user = User::new(user_json.as_object().unwrap().clone());
760 let abi_json = json! {
761 {
762 "spec": {},
763 "storage": {},
764 "types": []
765 }
766 }
767 .as_object()
768 .unwrap()
769 .clone();
770
771 let metadata = ContractMetadata::new(
772 source,
773 contract,
774 Some(String::from("useink/contracts-verifiable:6.0.0-beta")),
775 Some(user),
776 abi_json,
777 );
778 let json = serde_json::to_value(&metadata).unwrap();
779
780 let expected = json! {
781 {
782 "source": {
783 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
784 "language": "ink! 2.1.0",
785 "compiler": "rustc 1.46.0-nightly",
786 "contract_binary": "0x000102",
787 "build_info": {
788 "example_compiler_version": 42,
789 "example_settings": [],
790 "example_name": "increment"
791 }
792 },
793 "image": "useink/contracts-verifiable:6.0.0-beta",
794 "contract": {
795 "name": "incrementer",
796 "version": "2.1.0",
797 "authors": [
798 "Use Ink <ink@use.ink>"
799 ],
800 "description": "increment a value",
801 "documentation": "http://docs.rs/",
802 "repository": "http://github.com/use-ink/ink/",
803 "homepage": "http://example.com/",
804 "license": "Apache-2.0",
805 },
806 "user": {
807 "more-user-provided-fields": [
808 "and",
809 "their",
810 "values"
811 ],
812 "some-user-provided-field": "and-its-value"
813 },
814 "spec": {},
816 "storage": {},
817 "types": []
818 }
819 };
820
821 assert_eq!(json, expected);
822 }
823
824 #[test]
825 fn json_excludes_optional_fields() {
826 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
827 let compiler = SourceCompiler::new(
828 Compiler::RustC,
829 Version::parse("1.46.0-nightly").unwrap(),
830 );
831 let source = Source::new(None, CodeHash([0u8; 32]), language, compiler, None);
832 let contract = Contract::builder()
833 .name("incrementer")
834 .version(Version::new(2, 1, 0))
835 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
836 .build()
837 .unwrap();
838 let abi_json = json! {
839 {
840 "spec": {},
841 "storage": {},
842 "types": []
843 }
844 }
845 .as_object()
846 .unwrap()
847 .clone();
848
849 let metadata = ContractMetadata::new(source, contract, None, None, abi_json);
850 let json = serde_json::to_value(&metadata).unwrap();
851
852 let expected = json! {
853 {
854 "contract": {
855 "name": "incrementer",
856 "version": "2.1.0",
857 "authors": [
858 "Use Ink <ink@use.ink>"
859 ],
860 },
861 "image": serde_json::Value::Null,
862 "source": {
863 "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
864 "language": "ink! 2.1.0",
865 "compiler": "rustc 1.46.0-nightly"
866 },
867 "spec": {},
869 "storage": {},
870 "types": []
871 }
872 };
873
874 assert_eq!(json, expected);
875 }
876
877 #[test]
878 fn decoding_works() {
879 let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
880 let compiler = SourceCompiler::new(
881 Compiler::RustC,
882 Version::parse("1.46.0-nightly").unwrap(),
883 );
884 let contract_binary = SourceContractBinary::new(vec![0u8, 1u8, 2u8]);
885 let build_info = json! {
886 {
887 "example_compiler_version": 42,
888 "example_settings": [],
889 "example_name": "increment",
890 }
891 }
892 .as_object()
893 .unwrap()
894 .clone();
895
896 let source = Source::new(
897 Some(contract_binary),
898 CodeHash([0u8; 32]),
899 language,
900 compiler,
901 Some(build_info),
902 );
903 let contract = Contract::builder()
904 .name("incrementer")
905 .version(Version::new(2, 1, 0))
906 .authors(vec!["Use Ink <ink@use.ink>".to_string()])
907 .description("increment a value")
908 .documentation(Url::parse("http://docs.rs/").unwrap())
909 .repository(Url::parse("http://github.com/use-ink/ink/").unwrap())
910 .homepage(Url::parse("http://example.com/").unwrap())
911 .license("Apache-2.0")
912 .build()
913 .unwrap();
914
915 let user_json = json! {
916 {
917 "more-user-provided-fields": [
918 "and",
919 "their",
920 "values"
921 ],
922 "some-user-provided-field": "and-its-value"
923 }
924 };
925 let user = User::new(user_json.as_object().unwrap().clone());
926 let abi_json = json! {
927 {
928 "spec": {},
929 "storage": {},
930 "types": []
931 }
932 }
933 .as_object()
934 .unwrap()
935 .clone();
936
937 let metadata =
938 ContractMetadata::new(source, contract, None, Some(user), abi_json);
939 let json = serde_json::to_value(&metadata).unwrap();
940
941 let decoded = serde_json::from_value::<ContractMetadata>(json);
942 assert!(decoded.is_ok())
943 }
944}