contract.rs 4.9 KB

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