optimizations.rs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. // SPDX-License-Identifier: Apache-2.0
  2. use crate::{
  3. borsh_encoding::{visit_mut, VisitorMut},
  4. AccountMeta, BorshToken, Pubkey, VirtualMachineBuilder,
  5. };
  6. use anchor_syn::idl::types::IdlAccountItem;
  7. use once_cell::sync::Lazy;
  8. use rayon::iter::ParallelIterator;
  9. use rayon::prelude::IntoParallelIterator;
  10. use serde::Deserialize;
  11. use solang::codegen::Options;
  12. use std::{
  13. env::var,
  14. fs::{read_dir, read_to_string, File},
  15. io::BufReader,
  16. path::Path,
  17. };
  18. #[derive(Debug, Deserialize)]
  19. struct Calls {
  20. constructor: Vec<BorshToken>,
  21. function: Vec<(String, Vec<BorshToken>)>,
  22. }
  23. static NO_OPTIMIZATIONS: Lazy<Options> = Lazy::new(|| Options {
  24. dead_storage: false,
  25. constant_folding: false,
  26. strength_reduce: false,
  27. vector_to_slice: false,
  28. common_subexpression_elimination: false,
  29. ..Default::default()
  30. });
  31. #[test]
  32. fn optimizations() {
  33. let calls = Path::new("tests/optimization_testcases/calls");
  34. if let Ok(testname) = var("TESTNAME") {
  35. run_test(&calls.join(testname).with_extension("json"));
  36. } else {
  37. let tests = read_dir(calls)
  38. .unwrap()
  39. .map(|entry| entry.unwrap().path())
  40. .collect::<Vec<_>>();
  41. tests.into_par_iter().for_each(|path| run_test(&path));
  42. //tests.iter().for_each(|path| run_test(path));
  43. }
  44. }
  45. fn run_test(path: &Path) {
  46. let file_stem = path.file_stem().unwrap();
  47. // Known problematic test.
  48. if file_stem == "b6339ad75e9175a6bf332a2881001b6c928734e2" {
  49. return;
  50. }
  51. println!("testcase: {file_stem:?}");
  52. let file =
  53. File::open(path).unwrap_or_else(|error| panic!("failed to open {path:?}: {error:?}"));
  54. let reader = BufReader::new(file);
  55. let calls: Calls = serde_json::from_reader(reader).unwrap();
  56. let path = Path::new("tests/optimization_testcases/programs")
  57. .join(file_stem)
  58. .with_extension("sol");
  59. let program = read_to_string(path).unwrap();
  60. run_test_with_opts(
  61. &program,
  62. &calls,
  63. [Options::default(), NO_OPTIMIZATIONS.clone()],
  64. );
  65. }
  66. fn run_test_with_opts<T: IntoIterator<Item = Options>>(program: &str, calls: &Calls, opts: T) {
  67. let mut results_prev: Option<Vec<Result<Option<BorshToken>, u64>>> = None;
  68. for (i, opts) in opts.into_iter().enumerate() {
  69. println!("iteration: {i}");
  70. let mut results_curr = Vec::new();
  71. let mut vm = VirtualMachineBuilder::new(program).opts(opts).build();
  72. let data_account = vm.initialize_data_account();
  73. results_curr.push(
  74. vm.function("new")
  75. .arguments(&calls.constructor)
  76. .accounts(vec![("dataAccount", data_account)])
  77. .call_with_error_code(),
  78. );
  79. let program_id = vm.stack[0].id;
  80. for (name, args) in &calls.function {
  81. let needs_account = vm.stack[0]
  82. .idl
  83. .as_ref()
  84. .unwrap()
  85. .instructions
  86. .iter()
  87. .find(|instr| &instr.name == name)
  88. .unwrap()
  89. .accounts
  90. .iter()
  91. .any(|acc| match acc {
  92. IdlAccountItem::IdlAccount(account) => account.name == "dataAccount",
  93. IdlAccountItem::IdlAccounts(_) => false,
  94. });
  95. results_curr.push(if needs_account {
  96. vm.function(name)
  97. .arguments(args)
  98. .accounts(vec![("dataAccount", data_account)])
  99. .call_with_error_code()
  100. } else {
  101. vm.function(name)
  102. .arguments(args)
  103. .remaining_accounts(&[AccountMeta {
  104. pubkey: Pubkey(program_id),
  105. is_signer: false,
  106. is_writable: false,
  107. }])
  108. .call_with_error_code()
  109. });
  110. }
  111. for token in results_curr.iter_mut().flatten().flatten() {
  112. visit_mut(&mut AddressEraser, token);
  113. }
  114. if let Some(results_prev) = &results_prev {
  115. assert_eq!(results_prev, &results_curr);
  116. } else {
  117. results_prev = Some(results_curr);
  118. }
  119. }
  120. }
  121. // If `AddressEraser` were not used above, one would see failures with programs that return
  122. // addresses, e.g.:
  123. // thread 'solana_tests::optimizations::optimizations' panicked at 'assertion failed: `(left == right)`
  124. // left: `[Ok(Some(Address([/* one sequence of random bytes */])))]`,
  125. // right: `[Ok(Some(Address([/* another sequence of random bytes */])))]`', tests/solana_tests/optimizations.rs:105:13
  126. // d55b66a2225baa2bd6cd3641fff28de6fdf9b30e.sol is an example of such a program.
  127. struct AddressEraser;
  128. impl VisitorMut for AddressEraser {
  129. fn visit_address(&mut self, a: &mut [u8; 32]) {
  130. a.copy_from_slice(&[0u8; 32]);
  131. }
  132. }