contract.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // SPDX-License-Identifier: Apache-2.0
  2. use path_slash::PathExt;
  3. use rayon::prelude::*;
  4. use solang::{
  5. abi::generate_abi,
  6. codegen,
  7. file_resolver::FileResolver,
  8. parse_and_resolve,
  9. sema::{ast::Namespace, file::PathDisplay},
  10. Target,
  11. };
  12. use solang_parser::diagnostics::Level;
  13. use std::{
  14. ffi::OsStr,
  15. fs::{read_dir, File},
  16. io::{self, BufRead, BufReader, Read},
  17. path::{Path, PathBuf},
  18. };
  19. #[test]
  20. fn solana_contracts() -> io::Result<()> {
  21. contract_tests("tests/contract_testcases/solana", Target::Solana)
  22. }
  23. #[test]
  24. fn polkadot_contracts() -> io::Result<()> {
  25. contract_tests(
  26. "tests/contract_testcases/polkadot",
  27. Target::default_polkadot(),
  28. )
  29. }
  30. #[test]
  31. fn evm_contracts() -> io::Result<()> {
  32. contract_tests("tests/contract_testcases/evm", Target::EVM)
  33. }
  34. fn contract_tests(file_path: &str, target: Target) -> io::Result<()> {
  35. let path = PathBuf::from(file_path);
  36. recurse_directory(path, target)
  37. }
  38. fn recurse_directory(path: PathBuf, target: Target) -> io::Result<()> {
  39. let mut entries = Vec::new();
  40. for entry in read_dir(path)? {
  41. let path = entry?.path();
  42. if path.is_dir() {
  43. recurse_directory(path, target)?;
  44. } else if let Some(ext) = path.extension() {
  45. if ext.to_string_lossy() == "sol" {
  46. entries.push(path);
  47. }
  48. }
  49. }
  50. entries.into_par_iter().for_each(|entry| {
  51. parse_file(entry, target).unwrap();
  52. });
  53. Ok(())
  54. }
  55. fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
  56. let mut cache = FileResolver::default();
  57. let filename = add_file(&mut cache, &path, target)?;
  58. let mut ns = parse_and_resolve(OsStr::new(&filename), &mut cache, target);
  59. if !ns.diagnostics.any_errors() {
  60. // codegen all the contracts
  61. codegen::codegen(
  62. &mut ns,
  63. &codegen::Options {
  64. opt_level: codegen::OptimizationLevel::Default,
  65. ..Default::default()
  66. },
  67. );
  68. }
  69. #[cfg(windows)]
  70. {
  71. for file in &mut ns.files {
  72. let filename = file.path.to_slash_lossy().to_string();
  73. file.path = PathBuf::from(filename);
  74. }
  75. }
  76. check_diagnostics(&path, &ns);
  77. if !ns.diagnostics.any_errors() {
  78. // let's try and emit
  79. for contract_no in 0..ns.contracts.len() {
  80. let contract = &ns.contracts[contract_no];
  81. if contract.instantiable {
  82. let code = match ns.target {
  83. Target::Solana | Target::Polkadot { .. } => {
  84. contract.emit(&ns, &Default::default())
  85. }
  86. Target::EVM => b"beep".to_vec(),
  87. };
  88. let _ = generate_abi(contract_no, &ns, &code, false, &["unknown".into()], "0.1.0");
  89. }
  90. }
  91. }
  92. Ok(())
  93. }
  94. fn add_file(cache: &mut FileResolver, path: &Path, target: Target) -> io::Result<String> {
  95. let mut file = File::open(path)?;
  96. let mut source = String::new();
  97. file.read_to_string(&mut source)?;
  98. // make sure the path uses unix file separators, this is what the dot file uses
  99. let filename = path.to_slash_lossy();
  100. println!("Parsing {filename} for {target}");
  101. // The files may have had their end of lines mangled on Windows
  102. cache.set_file_contents(&filename, source.replace("\r\n", "\n"));
  103. for line in source.lines() {
  104. if line.starts_with("import") {
  105. if let (Some(start), Some(end)) = (line.find('"'), line.rfind('"')) {
  106. let file = &line[start + 1..end];
  107. if !file.is_empty() && file != "solana" {
  108. let mut import_path = path.parent().unwrap().to_path_buf();
  109. import_path.push(file);
  110. println!("adding import {}", import_path.display());
  111. add_file(cache, &import_path, target)?;
  112. }
  113. }
  114. }
  115. }
  116. Ok(filename.to_string())
  117. }
  118. fn check_diagnostics(path: &Path, ns: &Namespace) {
  119. let mut expected = "// ---- Expect: diagnostics ----\n".to_owned();
  120. for diag in ns.diagnostics.iter() {
  121. if diag.level == Level::Warning || diag.level == Level::Error {
  122. expected.push_str(&format!(
  123. "// {}: {}: {}\n",
  124. diag.level,
  125. ns.loc_to_string(PathDisplay::None, &diag.loc),
  126. diag.message
  127. ));
  128. for note in &diag.notes {
  129. expected.push_str(&format!(
  130. "// \tnote {}: {}\n",
  131. ns.loc_to_string(PathDisplay::None, &note.loc),
  132. note.message
  133. ));
  134. }
  135. }
  136. }
  137. let mut found = String::new();
  138. let file = File::open(path).unwrap();
  139. for line in BufReader::new(file).lines() {
  140. let line = line.unwrap();
  141. if found.is_empty() && !line.starts_with("// ---- Expect: diagnostics ----") {
  142. continue;
  143. }
  144. found.push_str(&line);
  145. found.push('\n');
  146. }
  147. assert!(!found.is_empty());
  148. assert_eq!(found, expected, "source: {}", path.display());
  149. }