1use crate::{util, Abigen, Context, ContractBindings, ContractFilter, ExpandedContract};
3use eyre::Result;
4use inflector::Inflector;
5use proc_macro2::TokenStream;
6use quote::quote;
7use std::{
8 collections::{BTreeMap, BTreeSet},
9 fs,
10 io::Write,
11 path::{Path, PathBuf},
12};
13use toml::Value;
14
15const DEFAULT_ETHERS_DEP: &str =
17 "ethers = { version = \"2\", default-features = false, features = [\"abigen\"] }";
18
19#[derive(Debug, Clone)]
22pub struct MultiAbigen {
23 abigens: Vec<Abigen>,
25}
26
27impl std::ops::Deref for MultiAbigen {
28 type Target = Vec<Abigen>;
29
30 fn deref(&self) -> &Self::Target {
31 &self.abigens
32 }
33}
34
35impl std::ops::DerefMut for MultiAbigen {
36 fn deref_mut(&mut self) -> &mut Self::Target {
37 &mut self.abigens
38 }
39}
40
41impl From<Vec<Abigen>> for MultiAbigen {
42 fn from(abigens: Vec<Abigen>) -> Self {
43 Self { abigens }
44 }
45}
46
47impl std::iter::FromIterator<Abigen> for MultiAbigen {
48 fn from_iter<I: IntoIterator<Item = Abigen>>(iter: I) -> Self {
49 iter.into_iter().collect::<Vec<_>>().into()
50 }
51}
52
53impl MultiAbigen {
54 pub fn new<I, Name, Source>(abis: I) -> Result<Self>
58 where
59 I: IntoIterator<Item = (Name, Source)>,
60 Name: AsRef<str>,
61 Source: AsRef<str>,
62 {
63 let abis = abis
64 .into_iter()
65 .map(|(contract_name, abi_source)| Abigen::new(contract_name.as_ref(), abi_source))
66 .collect::<Result<Vec<_>>>()?;
67
68 Ok(Self::from_abigens(abis))
69 }
70
71 pub fn from_abigens(abis: impl IntoIterator<Item = Abigen>) -> Self {
73 abis.into_iter().collect()
74 }
75
76 pub fn from_json_files(root: impl AsRef<Path>) -> Result<Self> {
97 let root = root.as_ref();
98 let files = util::json_files(root);
99 eyre::ensure!(!files.is_empty(), "No json files found in directory: {}", root.display());
100 files.into_iter().map(Abigen::from_file).collect()
101 }
102
103 #[must_use]
129 pub fn with_filter(mut self, filter: impl Into<ContractFilter>) -> Self {
130 self.apply_filter(&filter.into());
131 self
132 }
133
134 pub fn apply_filter(&mut self, filter: &ContractFilter) {
136 self.abigens.retain(|abi| filter.is_match(abi.contract_name.to_string()))
137 }
138
139 pub fn push(&mut self, abigen: Abigen) {
141 self.abigens.push(abigen)
142 }
143
144 pub fn build(self) -> Result<MultiBindings> {
146 let format = self.abigens.iter().any(|gen| gen.format);
147 Ok(MultiBindings {
148 expansion: MultiExpansion::from_abigen(self.abigens)?.expand(),
149 format,
150 dependencies: vec![],
151 })
152 }
153}
154
155pub struct MultiExpansion {
157 contracts: Vec<(ExpandedContract, Context)>,
159}
160
161impl MultiExpansion {
162 pub fn new(contracts: Vec<(ExpandedContract, Context)>) -> Self {
164 Self { contracts }
165 }
166
167 pub fn from_abigen(abigens: impl IntoIterator<Item = Abigen>) -> Result<Self> {
169 let contracts = abigens.into_iter().map(|abigen| abigen.expand()).collect::<Result<_>>()?;
170 Ok(Self::new(contracts))
171 }
172
173 pub fn expand_inplace(self) -> TokenStream {
177 self.expand().expand_inplace()
178 }
179
180 pub fn expand(self) -> MultiExpansionResult {
185 let mut expansions = self.contracts;
186 let mut shared_types = Vec::new();
187 let mut dirty_contracts = BTreeSet::new();
190
191 if expansions.len() > 1 {
193 let mut conflicts: BTreeMap<String, Vec<usize>> = BTreeMap::new();
195 for (idx, (_, ctx)) in expansions.iter().enumerate() {
196 for type_identifier in ctx.internal_structs().rust_type_names().keys() {
197 conflicts
198 .entry(type_identifier.clone())
199 .or_insert_with(|| Vec::with_capacity(1))
200 .push(idx);
201 }
202 }
203
204 for (id, contracts) in conflicts.iter().filter(|(_, c)| c.len() > 1) {
206 shared_types.push(
208 expansions[contracts[0]]
209 .1
210 .struct_definition(id)
211 .expect("struct def succeeded previously"),
212 );
213
214 for contract in contracts.iter().copied() {
216 expansions[contract].1.remove_struct(id);
217 dirty_contracts.insert(contract);
218 }
219 }
220
221 for contract in dirty_contracts.iter().copied() {
223 let (expanded, ctx) = &mut expansions[contract];
224 expanded.abi_structs = ctx.abi_structs().expect("struct def succeeded previously");
225 }
226 }
227
228 MultiExpansionResult { root: None, contracts: expansions, dirty_contracts, shared_types }
229 }
230}
231
232pub struct MultiExpansionResult {
234 root: Option<PathBuf>,
239 contracts: Vec<(ExpandedContract, Context)>,
240 dirty_contracts: BTreeSet<usize>,
242 shared_types: Vec<TokenStream>,
244}
245
246impl MultiExpansionResult {
247 pub fn expand_inplace(mut self) -> TokenStream {
249 let mut tokens = TokenStream::new();
250
251 let shared_types_module = quote! {__shared_types};
252 let shared_path = quote!(
254 pub use super::#shared_types_module::*;
255 );
256 self.add_shared_import_path(shared_path);
257
258 let Self { contracts, shared_types, .. } = self;
259
260 if !shared_types.is_empty() {
261 tokens.extend(quote! {
262 pub mod #shared_types_module {
263 #( #shared_types )*
264 }
265 });
266 }
267
268 tokens.extend(contracts.into_iter().map(|(exp, _)| exp.into_tokens()));
269
270 tokens
271 }
272
273 pub fn set_root(&mut self, root: impl Into<PathBuf>) {
278 self.root = Some(root.into());
279 }
280
281 fn set_shared_import_path(&mut self, single_file: bool) {
285 let shared_path = if single_file {
286 quote!(
287 pub use super::__shared_types::*;
288 )
289 } else {
290 quote!(
291 pub use super::super::shared_types::*;
292 )
293 };
294 self.add_shared_import_path(shared_path);
295 }
296
297 fn add_shared_import_path(&mut self, shared: TokenStream) {
299 for contract in self.dirty_contracts.iter().copied() {
300 let (expanded, ..) = &mut self.contracts[contract];
301 expanded.imports.extend(shared.clone());
302 }
303 }
304
305 fn into_bindings(
307 mut self,
308 single_file: bool,
309 format: bool,
310 dependencies: Vec<String>,
311 ) -> MultiBindingsInner {
312 self.set_shared_import_path(single_file);
313 let Self { contracts, shared_types, root, .. } = self;
314 let bindings = contracts
315 .into_iter()
316 .map(|(expanded, ctx)| ContractBindings {
317 tokens: expanded.into_tokens(),
318 format,
319 name: ctx.contract_name().to_string(),
320 })
321 .map(|v| (v.name.clone(), v))
322 .collect();
323
324 let shared_types = if !shared_types.is_empty() {
325 let shared_types = if single_file {
326 quote! {
327 pub mod __shared_types {
328 #( #shared_types )*
329 }
330 }
331 } else {
332 quote! {
333 #( #shared_types )*
334 }
335 };
336 Some(ContractBindings {
337 tokens: shared_types,
338 format,
339 name: "shared_types".to_string(),
340 })
341 } else {
342 None
343 };
344
345 MultiBindingsInner { root, bindings, shared_types, dependencies }
346 }
347}
348
349pub struct MultiBindings {
376 expansion: MultiExpansionResult,
377 format: bool,
378 dependencies: Vec<String>,
379}
380
381impl MultiBindings {
382 pub fn len(&self) -> usize {
384 self.expansion.contracts.len()
385 }
386
387 pub fn is_empty(&self) -> bool {
389 self.expansion.contracts.is_empty()
390 }
391
392 #[must_use]
393 #[deprecated = "Use format instead"]
394 #[doc(hidden)]
395 pub fn rustfmt(mut self, rustfmt: bool) -> Self {
396 self.format = rustfmt;
397 self
398 }
399
400 pub fn format(mut self, format: bool) -> Self {
405 self.format = format;
406 self
407 }
408
409 pub fn dependencies(
413 mut self,
414 dependencies: impl IntoIterator<Item = impl Into<String>>,
415 ) -> Self {
416 self.dependencies = dependencies.into_iter().map(|dep| dep.into()).collect();
417 self
418 }
419
420 fn into_inner(self, single_file: bool) -> MultiBindingsInner {
421 self.expansion.into_bindings(single_file, self.format, self.dependencies)
422 }
423
424 pub fn write_to_module(self, module: impl AsRef<Path>, single_file: bool) -> Result<()> {
455 self.into_inner(single_file).write_to_module(module, single_file)
456 }
457
458 pub fn write_to_crate(
496 self,
497 name: impl AsRef<str>,
498 version: impl AsRef<str>,
499 lib: impl AsRef<Path>,
500 single_file: bool,
501 ) -> Result<()> {
502 self.into_inner(single_file).write_to_crate(name, version, lib, single_file)
503 }
504
505 pub fn ensure_consistent_crate(
538 self,
539 name: impl AsRef<str>,
540 version: impl AsRef<str>,
541 crate_path: impl AsRef<Path>,
542 single_file: bool,
543 check_cargo_toml: bool,
544 ) -> Result<()> {
545 self.into_inner(single_file).ensure_consistent_crate(
546 name,
547 version,
548 crate_path,
549 single_file,
550 check_cargo_toml,
551 )
552 }
553
554 pub fn ensure_consistent_module(
587 self,
588 module: impl AsRef<Path>,
589 single_file: bool,
590 ) -> Result<()> {
591 self.into_inner(single_file).ensure_consistent_module(module, single_file)
592 }
593}
594
595struct MultiBindingsInner {
596 root: Option<PathBuf>,
601 bindings: BTreeMap<String, ContractBindings>,
603 shared_types: Option<ContractBindings>,
605 dependencies: Vec<String>,
608}
609
610impl std::ops::Deref for MultiBindingsInner {
612 type Target = BTreeMap<String, ContractBindings>;
613
614 fn deref(&self) -> &Self::Target {
615 &self.bindings
616 }
617}
618
619impl MultiBindingsInner {
620 fn generate_cargo_toml(
622 &self,
623 name: impl AsRef<str>,
624 version: impl AsRef<str>,
625 crate_version: String,
626 ) -> Result<Vec<u8>> {
627 let mut toml = vec![];
628
629 writeln!(toml, "[package]")?;
630 writeln!(toml, r#"name = "{}""#, name.as_ref())?;
631 writeln!(toml, r#"version = "{}""#, version.as_ref())?;
632 writeln!(toml, r#"edition = "2021""#)?;
633 writeln!(toml)?;
634 writeln!(toml, "# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html")?;
635 writeln!(toml)?;
636 writeln!(toml, "[dependencies]")?;
637 writeln!(toml, r#"{crate_version}"#)?;
638 for dependency in self.dependencies.clone() {
639 writeln!(toml, "{}", dependency)?;
640 }
641 Ok(toml)
642 }
643
644 fn crate_version(&self) -> String {
649 self.try_find_crate_version().unwrap_or_else(|_| DEFAULT_ETHERS_DEP.to_string())
650 }
651
652 fn try_find_crate_version(&self) -> Result<String> {
656 let cargo_toml =
657 if let Some(root) = self.root.clone() { root } else { std::env::current_dir()? }
658 .join("Cargo.toml");
659
660 if !cargo_toml.exists() {
661 return Ok(DEFAULT_ETHERS_DEP.to_string())
662 }
663
664 let data = fs::read_to_string(cargo_toml)?;
665 let toml = data.parse::<Value>()?;
666
667 let ethers = toml
668 .get("dependencies")
669 .and_then(|v| v.get("ethers").or_else(|| v.get("ethers-contract")))
670 .ok_or_else(|| eyre::eyre!("couldn't find ethers or ethers-contract dependency"))?;
671
672 if let Some(rev) = ethers.get("rev") {
673 Ok(format!("ethers = {{ git = \"https://github.com/gakonst/ethers-rs\", rev = {rev}, default-features = false, features = [\"abigen\"] }}"))
674 } else if let Some(version) = ethers.get("version") {
675 Ok(format!(
676 "ethers = {{ version = {version}, default-features = false, features = [\"abigen\"] }}"
677 ))
678 } else {
679 Ok(DEFAULT_ETHERS_DEP.to_string())
680 }
681 }
682
683 fn write_cargo_toml(
685 &self,
686 lib: &Path,
687 name: impl AsRef<str>,
688 version: impl AsRef<str>,
689 ) -> Result<()> {
690 let crate_version = self.crate_version();
691 let contents = self.generate_cargo_toml(name, version, crate_version)?;
692
693 let mut file = fs::OpenOptions::new()
694 .read(true)
695 .write(true)
696 .create_new(true)
697 .open(lib.join("Cargo.toml"))?;
698 file.write_all(&contents)?;
699
700 Ok(())
701 }
702
703 fn append_module_names(&self, mut buf: impl Write) -> Result<()> {
705 let mut mod_names: BTreeSet<_> =
706 self.bindings.keys().map(|name| util::safe_module_name(name)).collect();
707 if let Some(ref shared) = self.shared_types {
708 mod_names.insert(shared.name.to_snake_case());
709 }
710
711 for module in mod_names.into_iter().map(|name| format!("pub mod {name};")) {
712 writeln!(buf, "{module}")?;
713 }
714
715 Ok(())
716 }
717
718 fn generate_super_contents(&self, is_crate: bool, single_file: bool) -> Result<Vec<u8>> {
720 let mut contents = vec![];
721 generate_prefix(&mut contents, is_crate, single_file)?;
722
723 if single_file {
724 if let Some(ref shared) = self.shared_types {
725 shared.write(&mut contents)?;
726 }
727 for binding in self.bindings.values() {
728 binding.write(&mut contents)?;
729 }
730 } else {
731 self.append_module_names(&mut contents)?;
732 }
733
734 Ok(contents)
735 }
736
737 fn write_super_file(&self, path: &Path, is_crate: bool, single_file: bool) -> Result<()> {
739 let filename = if is_crate { "lib.rs" } else { "mod.rs" };
740 let contents = self.generate_super_contents(is_crate, single_file)?;
741 fs::write(path.join(filename), contents)?;
742 Ok(())
743 }
744
745 fn write_bindings(&self, path: &Path) -> Result<()> {
747 if let Some(ref shared) = self.shared_types {
748 shared.write_module_in_dir(path)?;
749 }
750 for binding in self.bindings.values() {
751 binding.write_module_in_dir(path)?;
752 }
753 Ok(())
754 }
755
756 fn write_to_module(self, module: impl AsRef<Path>, single_file: bool) -> Result<()> {
757 let module = module.as_ref();
758 fs::create_dir_all(module)?;
759
760 self.write_super_file(module, false, single_file)?;
761
762 if !single_file {
763 self.write_bindings(module)?;
764 }
765 Ok(())
766 }
767
768 fn write_to_crate(
769 self,
770 name: impl AsRef<str>,
771 version: impl AsRef<str>,
772 lib: impl AsRef<Path>,
773 single_file: bool,
774 ) -> Result<()> {
775 let lib = lib.as_ref();
776 let src = lib.join("src");
777 fs::create_dir_all(&src)?;
778
779 self.write_cargo_toml(lib, name, version)?;
780 self.write_super_file(&src, true, single_file)?;
781
782 if !single_file {
783 self.write_bindings(&src)?;
784 }
785
786 Ok(())
787 }
788
789 fn ensure_consistent_bindings(
794 self,
795 dir: impl AsRef<Path>,
796 is_crate: bool,
797 single_file: bool,
798 ) -> Result<()> {
799 let dir = dir.as_ref();
800 let super_name = if is_crate { "lib.rs" } else { "mod.rs" };
801
802 let super_contents = self.generate_super_contents(is_crate, single_file)?;
803 check_file_in_dir(dir, super_name, &super_contents)?;
804
805 if !single_file {
808 for binding in self.bindings.values() {
809 check_binding_in_dir(dir, binding)?;
810 }
811 }
812
813 Ok(())
814 }
815
816 fn ensure_consistent_crate(
817 self,
818 name: impl AsRef<str>,
819 version: impl AsRef<str>,
820 crate_path: impl AsRef<Path>,
821 single_file: bool,
822 check_cargo_toml: bool,
823 ) -> Result<()> {
824 let crate_path = crate_path.as_ref();
825
826 if check_cargo_toml {
827 let crate_version = self.crate_version();
829 let cargo_contents = self.generate_cargo_toml(name, version, crate_version)?;
830 check_file_in_dir(crate_path, "Cargo.toml", &cargo_contents)?;
831 }
832
833 self.ensure_consistent_bindings(crate_path.join("src"), true, single_file)?;
834 Ok(())
835 }
836
837 fn ensure_consistent_module(self, module: impl AsRef<Path>, single_file: bool) -> Result<()> {
838 self.ensure_consistent_bindings(module, false, single_file)?;
839 Ok(())
840 }
841}
842
843fn generate_prefix(mut buf: impl Write, is_crate: bool, single_file: bool) -> Result<()> {
845 writeln!(buf, "#![allow(clippy::all)]")?;
846 writeln!(
847 buf,
848 "//! This {} contains abigen! generated bindings for solidity contracts.",
849 if is_crate { "lib" } else { "module" }
850 )?;
851 writeln!(buf, "//! This is autogenerated code.")?;
852 writeln!(buf, "//! Do not manually edit these files.")?;
853 writeln!(
854 buf,
855 "//! {} may be overwritten by the codegen system at any time.",
856 if single_file && !is_crate { "This file" } else { "These files" }
857 )?;
858 Ok(())
859}
860
861fn check_file_in_dir(dir: &Path, file_name: &str, expected_contents: &[u8]) -> Result<()> {
862 eyre::ensure!(dir.is_dir(), "Not a directory: {}", dir.display());
863
864 let file_path = dir.join(file_name);
865 eyre::ensure!(file_path.is_file(), "Not a file: {}", file_path.display());
866
867 let contents = fs::read(&file_path).expect("Unable to read file");
868 eyre::ensure!(contents == expected_contents, format!("The contents of `{}` do not match the expected output of the newest `ethers::Abigen` version.\
869This indicates that the existing bindings are outdated and need to be generated again.", file_path.display()));
870 Ok(())
871}
872
873fn check_binding_in_dir(dir: &Path, binding: &ContractBindings) -> Result<()> {
874 let name = binding.module_filename();
875 let contents = binding.to_vec();
876
877 check_file_in_dir(dir, &name, &contents)?;
878 Ok(())
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use crate::{ExcludeContracts, SelectContracts};
885 use std::env;
886
887 struct Context {
888 multi_gen: MultiAbigen,
889 mod_root: PathBuf,
890 }
891
892 fn run_test<T>(test: T)
893 where
894 T: FnOnce(Context),
895 {
896 let crate_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
897 let console = Abigen::new(
898 "Console",
899 crate_root.join("../tests/solidity-contracts/console.json").display().to_string(),
900 )
901 .unwrap();
902
903 let simple_storage = Abigen::new(
904 "SimpleStorage",
905 crate_root.join("../tests/solidity-contracts/SimpleStorage.json").display().to_string(),
906 )
907 .unwrap();
908
909 let human_readable = Abigen::new(
910 "HrContract",
911 r"[
912 struct Foo { uint256 x; }
913 function foo(Foo memory x)
914 function bar(uint256 x, uint256 y, address addr)
915 yeet(uint256,uint256,address)
916 ]",
917 )
918 .unwrap();
919
920 let multi_gen = MultiAbigen::from_abigens([console, simple_storage, human_readable]);
921
922 let tmp = tempfile::tempdir().unwrap();
923 let mod_root = tmp.path().join("contracts");
924 let context = Context { multi_gen, mod_root };
926 test(context)
927 }
928
929 #[test]
930 fn can_generate_multi_file_module() {
931 run_test(|context| {
932 let Context { multi_gen, mod_root } = context;
933
934 let single_file = false;
935
936 multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
937 multi_gen
938 .build()
939 .unwrap()
940 .ensure_consistent_module(mod_root, single_file)
941 .expect("Inconsistent bindings");
942 })
943 }
944
945 #[test]
946 fn can_find_ethers_dep() {
947 run_test(|context| {
948 let Context { multi_gen, mod_root } = context;
949
950 let single_file = true;
951 let mut inner = multi_gen.build().unwrap().into_inner(single_file);
952 inner.root = Some(PathBuf::from("this does not exist"));
953 inner.write_to_module(mod_root, single_file).unwrap();
954 })
955 }
956
957 #[test]
958 fn can_generate_single_file_module() {
959 run_test(|context| {
960 let Context { multi_gen, mod_root } = context;
961
962 let single_file = true;
963
964 multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
965 multi_gen
966 .build()
967 .unwrap()
968 .ensure_consistent_module(mod_root, single_file)
969 .expect("Inconsistent bindings");
970 })
971 }
972
973 #[test]
974 fn can_generate_multi_file_crate() {
975 run_test(|context| {
976 let Context { multi_gen, mod_root } = context;
977
978 let single_file = false;
979 let name = "a-name";
980 let version = "290.3782.3";
981
982 multi_gen
983 .clone()
984 .build()
985 .unwrap()
986 .write_to_crate(name, version, &mod_root, single_file)
987 .unwrap();
988 multi_gen
989 .build()
990 .unwrap()
991 .ensure_consistent_crate(name, version, mod_root, single_file, true)
992 .expect("Inconsistent bindings");
993 })
994 }
995
996 #[test]
997 fn can_generate_single_file_crate() {
998 run_test(|context| {
999 let Context { multi_gen, mod_root } = context;
1000
1001 let single_file = true;
1002 let name = "a-name";
1003 let version = "290.3782.3";
1004
1005 multi_gen
1006 .clone()
1007 .build()
1008 .unwrap()
1009 .write_to_crate(name, version, &mod_root, single_file)
1010 .unwrap();
1011 multi_gen
1012 .build()
1013 .unwrap()
1014 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1015 .expect("Inconsistent bindings");
1016 })
1017 }
1018
1019 #[test]
1020 fn can_detect_incosistent_multi_file_module() {
1021 run_test(|context| {
1022 let Context { mut multi_gen, mod_root } = context;
1023
1024 let single_file = false;
1025
1026 multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
1027
1028 multi_gen.push(
1029 Abigen::new(
1030 "AdditionalContract",
1031 r"[
1032 getValue() (uint256)
1033 ]",
1034 )
1035 .unwrap(),
1036 );
1037
1038 let result =
1039 multi_gen.build().unwrap().ensure_consistent_module(mod_root, single_file).is_err();
1040
1041 assert!(result, "Inconsistent bindings wrongly approved");
1043 })
1044 }
1045
1046 #[test]
1047 fn can_detect_incosistent_single_file_module() {
1048 run_test(|context| {
1049 let Context { mut multi_gen, mod_root } = context;
1050
1051 let single_file = true;
1052
1053 multi_gen.clone().build().unwrap().write_to_module(&mod_root, single_file).unwrap();
1054
1055 multi_gen.push(
1056 Abigen::new(
1057 "AdditionalContract",
1058 r"[
1059 getValue() (uint256)
1060 ]",
1061 )
1062 .unwrap(),
1063 );
1064
1065 let result =
1066 multi_gen.build().unwrap().ensure_consistent_module(mod_root, single_file).is_err();
1067
1068 assert!(result, "Inconsistent bindings wrongly approved");
1070 })
1071 }
1072
1073 #[test]
1074 fn can_detect_incosistent_multi_file_crate() {
1075 run_test(|context| {
1076 let Context { mut multi_gen, mod_root } = context;
1077
1078 let single_file = false;
1079 let name = "a-name";
1080 let version = "290.3782.3";
1081
1082 multi_gen
1083 .clone()
1084 .build()
1085 .unwrap()
1086 .write_to_crate(name, version, &mod_root, single_file)
1087 .unwrap();
1088
1089 multi_gen.push(
1090 Abigen::new(
1091 "AdditionalContract",
1092 r"[
1093 getValue() (uint256)
1094 ]",
1095 )
1096 .unwrap(),
1097 );
1098
1099 let result = multi_gen
1100 .build()
1101 .unwrap()
1102 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1103 .is_err();
1104
1105 assert!(result, "Inconsistent bindings wrongly approved");
1107 })
1108 }
1109
1110 #[test]
1111 fn can_detect_inconsistent_single_file_crate() {
1112 run_test(|context| {
1113 let Context { mut multi_gen, mod_root } = context;
1114
1115 let single_file = true;
1116 let name = "a-name";
1117 let version = "290.3782.3";
1118
1119 multi_gen
1120 .clone()
1121 .build()
1122 .unwrap()
1123 .write_to_crate(name, version, &mod_root, single_file)
1124 .unwrap();
1125
1126 multi_gen.push(
1127 Abigen::new(
1128 "AdditionalContract",
1129 r"[
1130 getValue() (uint256)
1131 ]",
1132 )
1133 .unwrap(),
1134 );
1135
1136 let result = multi_gen
1137 .build()
1138 .unwrap()
1139 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1140 .is_err();
1141
1142 assert!(result, "Inconsistent bindings wrongly approved");
1144 })
1145 }
1146
1147 #[test]
1148 fn does_not_generate_shared_types_if_empty() {
1149 let gen = Abigen::new(
1150 "Greeter",
1151 r"[
1152 struct Inner {bool a;}
1153 greet1() (uint256)
1154 greet2(Inner inner) (string)
1155 ]",
1156 )
1157 .unwrap();
1158
1159 let tokens = MultiExpansion::new(vec![gen.expand().unwrap()]).expand_inplace().to_string();
1160 assert!(!tokens.contains("mod __shared_types"));
1161 }
1162
1163 #[test]
1164 fn can_filter_abigen() {
1165 let abi = Abigen::new(
1166 "MyGreeter",
1167 r"[
1168 greet() (string)
1169 ]",
1170 )
1171 .unwrap();
1172 let mut gen = MultiAbigen::from_abigens(vec![abi]).with_filter(ContractFilter::All);
1173 assert_eq!(gen.abigens.len(), 1);
1174 gen.apply_filter(&SelectContracts::default().add_name("MyGreeter").into());
1175 assert_eq!(gen.abigens.len(), 1);
1176
1177 gen.apply_filter(&ExcludeContracts::default().add_name("MyGreeter2").into());
1178 assert_eq!(gen.abigens.len(), 1);
1179
1180 let filtered = gen.clone().with_filter(SelectContracts::default().add_name("MyGreeter2"));
1181 assert!(filtered.abigens.is_empty());
1182
1183 let filtered = gen.clone().with_filter(ExcludeContracts::default().add_name("MyGreeter"));
1184 assert!(filtered.abigens.is_empty());
1185
1186 let filtered =
1187 gen.clone().with_filter(SelectContracts::default().add_pattern("MyGreeter2"));
1188 assert!(filtered.abigens.is_empty());
1189
1190 let filtered =
1191 gen.clone().with_filter(ExcludeContracts::default().add_pattern("MyGreeter"));
1192 assert!(filtered.abigens.is_empty());
1193
1194 gen.push(
1195 Abigen::new(
1196 "MyGreeterTest",
1197 r"[
1198 greet() (string)
1199 ]",
1200 )
1201 .unwrap(),
1202 );
1203 let filtered = gen.clone().with_filter(SelectContracts::default().add_pattern(".*Test"));
1204 assert_eq!(filtered.abigens.len(), 1);
1205 assert_eq!(filtered.abigens[0].contract_name, "MyGreeterTest");
1206
1207 let filtered = gen.clone().with_filter(ExcludeContracts::default().add_pattern(".*Test"));
1208 assert_eq!(filtered.abigens.len(), 1);
1209 assert_eq!(filtered.abigens[0].contract_name, "MyGreeter");
1210 }
1211
1212 #[test]
1213 fn can_deduplicate_types() {
1214 let root = tempfile::tempdir().unwrap();
1215 let json_files = "../tests/solidity-contracts/greeter";
1216
1217 let gen = MultiAbigen::from_json_files(json_files).unwrap();
1218 let bindings = gen.clone().build().unwrap();
1219 let single_file_dir = root.path().join("single_bindings");
1220 bindings.write_to_module(&single_file_dir, true).unwrap();
1221
1222 let single_file_mod = single_file_dir.join("mod.rs");
1223 assert!(single_file_mod.exists());
1224 let content = fs::read_to_string(&single_file_mod).unwrap();
1225 assert!(content.contains("mod __shared_types"));
1226 assert!(content.contains("pub struct Inner"));
1227 assert!(content.contains("pub struct Stuff"));
1228
1229 let bindings = gen.build().unwrap();
1231 let multi_file_dir = root.path().join("multi_bindings");
1232 bindings.write_to_module(&multi_file_dir, false).unwrap();
1233 let multi_file_mod = multi_file_dir.join("mod.rs");
1234 assert!(multi_file_mod.exists());
1235 let content = fs::read_to_string(&multi_file_mod).unwrap();
1236 assert!(content.contains("pub mod shared_types"));
1237
1238 let greeter1 = multi_file_dir.join("greeter_1.rs");
1239 assert!(greeter1.exists());
1240 let content = fs::read_to_string(&greeter1).unwrap();
1241 assert!(!content.contains("pub struct Inner"));
1242 assert!(!content.contains("pub struct Stuff"));
1243
1244 let greeter2 = multi_file_dir.join("greeter_2.rs");
1245 assert!(greeter2.exists());
1246 let content = fs::read_to_string(&greeter2).unwrap();
1247 assert!(!content.contains("pub struct Inner"));
1248 assert!(!content.contains("pub struct Stuff"));
1249
1250 let shared_types = multi_file_dir.join("shared_types.rs");
1251 assert!(shared_types.exists());
1252 let content = fs::read_to_string(&shared_types).unwrap();
1253 assert!(content.contains("pub struct Inner"));
1254 assert!(content.contains("pub struct Stuff"));
1255 }
1256
1257 #[test]
1258 fn can_sanitize_reserved_words() {
1259 let root = tempfile::tempdir().unwrap();
1260 let json_files = "../tests/solidity-contracts/ReservedWords";
1261
1262 let gen = MultiAbigen::from_json_files(json_files).unwrap();
1263 let bindings = gen.clone().build().unwrap();
1264 let single_file_dir = root.path().join("single_bindings");
1265 bindings.write_to_module(&single_file_dir, true).unwrap();
1266
1267 let single_file_mod = single_file_dir.join("mod.rs");
1268 assert!(single_file_mod.exists());
1269 let content = fs::read_to_string(&single_file_mod).unwrap();
1270 assert!(content.contains("pub mod mod_ {"));
1271 assert!(content.contains("pub mod enum_ {"));
1272
1273 let bindings = gen.build().unwrap();
1275 let multi_file_dir = root.path().join("multi_bindings");
1276 bindings.write_to_module(&multi_file_dir, false).unwrap();
1277 let multi_file_mod = multi_file_dir.join("mod.rs");
1278 assert!(multi_file_mod.exists());
1279 let content = fs::read_to_string(&multi_file_mod).unwrap();
1280 assert!(content.contains("pub mod enum_;"));
1281 assert!(content.contains("pub mod mod_;"));
1282
1283 let enum_ = multi_file_dir.join("enum_.rs");
1284 assert!(enum_.exists());
1285 let content = fs::read_to_string(&enum_).unwrap();
1286 assert!(content.contains("pub mod enum_ {"));
1287
1288 let mod_ = multi_file_dir.join("mod_.rs");
1289 assert!(mod_.exists());
1290 let content = fs::read_to_string(&mod_).unwrap();
1291 assert!(content.contains("pub mod mod_ {"));
1292 }
1293
1294 #[test]
1295 fn parse_ethers_crate() {
1296 run_test(|context| {
1298 let Context { multi_gen, mod_root } = context;
1299 let manifest = r#"
1300[package]
1301 name = "ethers-contract"
1302 version = "1.0.0"
1303 edition = "2021"
1304 rust-version = "1.64"
1305 authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
1306 license = "MIT OR Apache-2.0"
1307 description = "Smart contract bindings for the ethers-rs crate"
1308 homepage = "https://docs.rs/ethers"
1309 repository = "https://github.com/gakonst/ethers-rs"
1310 keywords = ["ethereum", "web3", "celo", "ethers"]
1311
1312 [dependencies]
1313 ethers-providers = { version = "^1.0.0", path = "../ethers-providers", default-features = false }
1314"#;
1315
1316 let root = mod_root.parent().unwrap();
1317 fs::write(root.join("../Cargo.toml"), manifest).unwrap();
1318 env::set_var("CARGO_MANIFEST_DIR", root);
1319 let single_file = false;
1320 let name = "a-name";
1321 let version = "290.3782.3";
1322
1323 multi_gen
1324 .clone()
1325 .build()
1326 .unwrap()
1327 .write_to_crate(name, version, &mod_root, single_file)
1328 .unwrap();
1329
1330 multi_gen
1331 .build()
1332 .unwrap()
1333 .ensure_consistent_crate(name, version, &mod_root, single_file, true)
1334 .expect("Inconsistent bindings");
1335 });
1336
1337 run_test(|context| {
1338 let Context { multi_gen, mod_root } = context;
1339
1340 let manifest = r#"
1341 [package]
1342 name = "ethers-contract"
1343 version = "1.0.0"
1344 edition = "2021"
1345 rust-version = "1.64"
1346 authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
1347 license = "MIT OR Apache-2.0"
1348 description = "Smart contract bindings for the ethers-rs crate"
1349 homepage = "https://docs.rs/ethers"
1350 repository = "https://github.com/gakonst/ethers-rs"
1351 keywords = ["ethereum", "web3", "celo", "ethers"]
1352
1353 [dependencies]
1354 ethers-contracts = "0.4.0"
1355"#;
1356
1357 let root = mod_root.parent().unwrap();
1358 fs::write(root.join("../Cargo.toml"), manifest).unwrap();
1359 env::set_var("CARGO_MANIFEST_DIR", root);
1360
1361 let single_file = false;
1362 let name = "a-name";
1363 let version = "290.3782.3";
1364
1365 multi_gen
1366 .clone()
1367 .build()
1368 .unwrap()
1369 .write_to_crate(name, version, &mod_root, single_file)
1370 .unwrap();
1371
1372 multi_gen
1373 .build()
1374 .unwrap()
1375 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1376 .expect("Inconsistent bindings");
1377 });
1378
1379 run_test(|context| {
1380 let Context { multi_gen, mod_root } = context;
1381
1382 let manifest = r#"
1383[package]
1384 name = "ethers-contract"
1385 version = "1.0.0"
1386 edition = "2021"
1387 rust-version = "1.64"
1388 authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
1389 license = "MIT OR Apache-2.0"
1390 description = "Smart contract bindings for the ethers-rs crate"
1391 homepage = "https://docs.rs/ethers"
1392 repository = "https://github.com/gakonst/ethers-rs"
1393 keywords = ["ethereum", "web3", "celo", "ethers"]
1394
1395 [dependencies]
1396 ethers = {git="https://github.com/gakonst/ethers-rs", rev = "fd8ebf5",features = ["ws", "rustls", "ipc"] }
1397"#;
1398
1399 let root = mod_root.parent().unwrap();
1400 fs::write(root.join("../Cargo.toml"), manifest).unwrap();
1401 env::set_var("CARGO_MANIFEST_DIR", root);
1402
1403 let single_file = false;
1404 let name = "a-name";
1405 let version = "290.3782.3";
1406
1407 multi_gen
1408 .clone()
1409 .build()
1410 .unwrap()
1411 .write_to_crate(name, version, &mod_root, single_file)
1412 .unwrap();
1413
1414 multi_gen
1415 .build()
1416 .unwrap()
1417 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1418 .expect("Inconsistent bindings");
1419 });
1420
1421 run_test(|context| {
1422 let Context { multi_gen, mod_root } = context;
1423
1424 let manifest = r#"
1425[package]
1426 name = "ethers-contract"
1427 version = "1.0.0"
1428 edition = "2021"
1429 rust-version = "1.64"
1430 authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
1431 license = "MIT OR Apache-2.0"
1432 description = "Smart contract bindings for the ethers-rs crate"
1433 homepage = "https://docs.rs/ethers"
1434 repository = "https://github.com/gakonst/ethers-rs"
1435 keywords = ["ethereum", "web3", "celo", "ethers"]
1436
1437 [dependencies]
1438 ethers = {git = "https://github.com/gakonst/ethers-rs", features = ["ws", "rustls", "ipc"] }
1439"#;
1440
1441 let root = mod_root.parent().unwrap();
1442 fs::write(root.join("../Cargo.toml"), manifest).unwrap();
1443 env::set_var("CARGO_MANIFEST_DIR", root);
1444
1445 let single_file = false;
1446 let name = "a-name";
1447 let version = "290.3782.3";
1448
1449 multi_gen
1450 .clone()
1451 .build()
1452 .unwrap()
1453 .write_to_crate(name, version, &mod_root, single_file)
1454 .unwrap();
1455
1456 multi_gen
1457 .build()
1458 .unwrap()
1459 .ensure_consistent_crate(name, version, mod_root, single_file, true)
1460 .expect("Inconsistent bindings");
1461 });
1462 }
1463}