account_collection.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. // SPDX-License-Identifier: Apache-2.0
  2. use crate::codegen::cfg::{ASTFunction, ControlFlowGraph, Instr, InternalCallTy};
  3. use crate::codegen::solana_accounts::account_from_number;
  4. use crate::codegen::{Builtin, Expression};
  5. use crate::sema::ast::{Contract, Function, Namespace, SolanaAccount};
  6. use crate::sema::diagnostics::Diagnostics;
  7. use crate::sema::solana_accounts::BuiltinAccounts;
  8. use crate::sema::Recurse;
  9. use indexmap::IndexSet;
  10. use solang_parser::diagnostics::Diagnostic;
  11. use solang_parser::pt;
  12. use solang_parser::pt::{FunctionTy, Loc};
  13. use std::collections::{HashSet, VecDeque};
  14. /// Struct to save the recursion data when traversing all the CFG instructions
  15. struct RecurseData<'a> {
  16. /// next_queue saves the set of functions we must check in the next iteration
  17. next_queue: IndexSet<(usize, usize)>,
  18. /// The number of the function we are currently traversing
  19. cfg_func_no: usize,
  20. /// The contract the function belongs to
  21. contract_no: usize,
  22. /// The quantity of accounts we have added the the hashmap 'accounts'
  23. accounts_added: usize,
  24. /// The number of the AST function we are currently traversing
  25. ast_no: usize,
  26. /// The namespace contracts
  27. contracts: &'a [Contract],
  28. /// The vector of functions from the contract
  29. functions: &'a [Function],
  30. diagnostics: &'a mut Diagnostics,
  31. }
  32. impl RecurseData<'_> {
  33. /// Add an account to the function's indexmap
  34. fn add_account(&mut self, account_name: String, account: &SolanaAccount) {
  35. let (is_signer, is_writer) = self.functions[self.ast_no]
  36. .solana_accounts
  37. .borrow()
  38. .get(&account_name)
  39. .map(|acc| (acc.is_signer, acc.is_writer))
  40. .unwrap_or((false, false));
  41. if self.functions[self.ast_no]
  42. .solana_accounts
  43. .borrow_mut()
  44. .insert(
  45. account_name,
  46. SolanaAccount {
  47. loc: account.loc,
  48. is_signer: account.is_signer || is_signer,
  49. is_writer: account.is_writer || is_writer,
  50. generated: true,
  51. },
  52. )
  53. .is_none()
  54. {
  55. self.accounts_added += 1;
  56. }
  57. }
  58. /// Add the system account to the function's indexmap
  59. fn add_system_account(&mut self) {
  60. self.add_account(
  61. BuiltinAccounts::SystemAccount.to_string(),
  62. &SolanaAccount {
  63. loc: Loc::Codegen,
  64. is_writer: false,
  65. is_signer: false,
  66. generated: true,
  67. },
  68. );
  69. }
  70. }
  71. /// Collect the accounts this contract needs
  72. pub(crate) fn collect_accounts_from_contract(contract_no: usize, ns: &Namespace) -> Diagnostics {
  73. let mut visiting_queue: IndexSet<(usize, usize)> = IndexSet::new();
  74. let mut diagnostics = Diagnostics::default();
  75. for func_no in ns.contracts[contract_no].all_functions.keys() {
  76. if ns.functions[*func_no].is_public()
  77. && !matches!(
  78. ns.functions[*func_no].ty,
  79. FunctionTy::Fallback | FunctionTy::Receive | FunctionTy::Modifier
  80. )
  81. {
  82. let func = &ns.functions[*func_no];
  83. let index = func
  84. .solana_accounts
  85. .borrow()
  86. .get_index_of(BuiltinAccounts::DataAccount.as_str());
  87. if let Some(data_account_index) = index {
  88. // Enforce the data account to be the first
  89. func.solana_accounts
  90. .borrow_mut()
  91. .move_index(data_account_index, 0);
  92. }
  93. if func.is_constructor() && func.has_payer_annotation() {
  94. func.solana_accounts.borrow_mut().insert(
  95. BuiltinAccounts::SystemAccount.to_string(),
  96. SolanaAccount {
  97. loc: Loc::Codegen,
  98. is_signer: false,
  99. is_writer: false,
  100. generated: true,
  101. },
  102. );
  103. }
  104. }
  105. visiting_queue.insert((
  106. contract_no,
  107. ns.contracts[contract_no].all_functions[func_no],
  108. ));
  109. }
  110. let mut recurse_data = RecurseData {
  111. next_queue: IndexSet::new(),
  112. cfg_func_no: 0,
  113. ast_no: 0,
  114. accounts_added: 0,
  115. contract_no,
  116. functions: &ns.functions,
  117. contracts: &ns.contracts,
  118. diagnostics: &mut diagnostics,
  119. };
  120. let mut old_size: usize = 0;
  121. loop {
  122. for (contract_no, func_no) in &visiting_queue {
  123. if *func_no == usize::MAX {
  124. continue;
  125. }
  126. recurse_data.contract_no = *contract_no;
  127. recurse_data.cfg_func_no = *func_no;
  128. match &ns.contracts[*contract_no].cfg[*func_no].function_no {
  129. ASTFunction::SolidityFunction(ast_no) | ASTFunction::YulFunction(ast_no) => {
  130. recurse_data.ast_no = *ast_no;
  131. }
  132. _ => (),
  133. }
  134. check_function(&ns.contracts[*contract_no].cfg[*func_no], &mut recurse_data);
  135. }
  136. // This is the convergence condition for this loop.
  137. // If we have not added new accounts to the map in this iteration and the queue for the
  138. // next iteration does not have any new element, we can stop.
  139. if old_size == recurse_data.accounts_added
  140. && visiting_queue.len() == recurse_data.next_queue.len()
  141. {
  142. break;
  143. }
  144. old_size = recurse_data.accounts_added;
  145. std::mem::swap(&mut visiting_queue, &mut recurse_data.next_queue);
  146. recurse_data.next_queue.clear();
  147. }
  148. diagnostics
  149. }
  150. /// Collect the accounts in a function
  151. fn check_function(cfg: &ControlFlowGraph, data: &mut RecurseData) {
  152. if cfg.blocks.is_empty() {
  153. return;
  154. }
  155. let mut queue: VecDeque<usize> = VecDeque::new();
  156. let mut visited: HashSet<usize> = HashSet::new();
  157. queue.push_back(0);
  158. visited.insert(0);
  159. while let Some(cur_block) = queue.pop_front() {
  160. for instr in &cfg.blocks[cur_block].instr {
  161. check_instruction(instr, data);
  162. }
  163. // TODO: Block edges is an expensive function, we use it six times throughout the code,
  164. // perhaps we can just use the dag I calculate during cse.
  165. // Changes in constant folding would be necessary
  166. for edge in cfg.blocks[cur_block].successors() {
  167. if !visited.contains(&edge) {
  168. queue.push_back(edge);
  169. visited.insert(edge);
  170. }
  171. }
  172. }
  173. }
  174. /// Collect the accounts in an instruction
  175. fn check_instruction(instr: &Instr, data: &mut RecurseData) {
  176. match instr {
  177. Instr::Print { expr }
  178. | Instr::LoadStorage { storage: expr, .. }
  179. | Instr::ClearStorage { storage: expr, .. }
  180. | Instr::BranchCond { cond: expr, .. }
  181. | Instr::PopStorage { storage: expr, .. }
  182. | Instr::SelfDestruct { recipient: expr }
  183. | Instr::Set { expr, .. } => {
  184. expr.recurse(data, check_expression);
  185. }
  186. Instr::Call { call, args, .. } => {
  187. if let InternalCallTy::Static { cfg_no } = call {
  188. // When we have an internal call, we analyse the current function again and the
  189. // function we are calling. This will guarantee convergence when there are
  190. // recursive function calls
  191. data.next_queue.insert((data.contract_no, *cfg_no));
  192. data.next_queue.insert((data.contract_no, data.cfg_func_no));
  193. match &data.contracts[data.contract_no].cfg[*cfg_no].function_no {
  194. ASTFunction::SolidityFunction(ast_no) | ASTFunction::YulFunction(ast_no) => {
  195. let accounts_to_add =
  196. data.functions[*ast_no].solana_accounts.borrow().clone();
  197. for (account_name, account) in accounts_to_add {
  198. data.add_account(account_name, &account);
  199. }
  200. }
  201. _ => (),
  202. }
  203. } else if let InternalCallTy::Builtin { ast_func_no } = call {
  204. let name = &data.functions[*ast_func_no].name;
  205. if name == "create_program_address" {
  206. data.add_system_account();
  207. }
  208. }
  209. for item in args {
  210. item.recurse(data, check_expression);
  211. }
  212. }
  213. Instr::Return { value } => {
  214. for item in value {
  215. item.recurse(data, check_expression);
  216. }
  217. }
  218. Instr::Branch { .. }
  219. | Instr::Nop
  220. | Instr::ReturnCode { .. }
  221. | Instr::PopMemory { .. }
  222. | Instr::Unimplemented { .. } => {}
  223. Instr::Store {
  224. dest,
  225. data: store_data,
  226. } => {
  227. dest.recurse(data, check_expression);
  228. store_data.recurse(data, check_expression);
  229. }
  230. Instr::AssertFailure { encoded_args } => {
  231. if let Some(args) = encoded_args {
  232. args.recurse(data, check_expression);
  233. }
  234. }
  235. Instr::ValueTransfer {
  236. address: expr1,
  237. value: expr2,
  238. ..
  239. }
  240. | Instr::ReturnData {
  241. data: expr1,
  242. data_len: expr2,
  243. }
  244. | Instr::SetStorage {
  245. value: expr1,
  246. storage: expr2,
  247. ..
  248. } => {
  249. expr1.recurse(data, check_expression);
  250. expr2.recurse(data, check_expression);
  251. }
  252. Instr::WriteBuffer {
  253. buf: expr_1,
  254. offset: expr_2,
  255. value: expr_3,
  256. }
  257. | Instr::MemCopy {
  258. source: expr_1,
  259. destination: expr_2,
  260. bytes: expr_3,
  261. }
  262. | Instr::SetStorageBytes {
  263. value: expr_1,
  264. storage: expr_2,
  265. offset: expr_3,
  266. } => {
  267. expr_1.recurse(data, check_expression);
  268. expr_2.recurse(data, check_expression);
  269. expr_3.recurse(data, check_expression);
  270. }
  271. Instr::PushStorage {
  272. value: opt_expr,
  273. storage: expr,
  274. ..
  275. } => {
  276. if let Some(opt_expr) = opt_expr {
  277. opt_expr.recurse(data, check_expression);
  278. }
  279. expr.recurse(data, check_expression);
  280. }
  281. Instr::PushMemory { value, .. } => {
  282. value.recurse(data, check_expression);
  283. }
  284. Instr::Constructor {
  285. loc,
  286. encoded_args,
  287. value,
  288. gas,
  289. salt,
  290. address,
  291. seeds,
  292. accounts,
  293. constructor_no,
  294. contract_no,
  295. ..
  296. } => {
  297. encoded_args.recurse(data, check_expression);
  298. if let Some(value) = value {
  299. value.recurse(data, check_expression);
  300. }
  301. gas.recurse(data, check_expression);
  302. if let Some(salt) = salt {
  303. salt.recurse(data, check_expression);
  304. }
  305. if let Some(address) = address {
  306. address.recurse(data, check_expression);
  307. }
  308. if let Some(seeds) = seeds {
  309. seeds.recurse(data, check_expression);
  310. }
  311. if let Some(accounts) = accounts {
  312. accounts.recurse(data, check_expression);
  313. } else {
  314. // If the one passes the AccountMeta vector to the constructor call, there is no
  315. // need to collect accounts for the IDL.
  316. if let Some(constructor_no) = constructor_no {
  317. transfer_accounts(loc, *contract_no, *constructor_no, data, false);
  318. }
  319. }
  320. data.add_system_account();
  321. }
  322. Instr::ExternalCall {
  323. loc,
  324. address,
  325. accounts,
  326. seeds,
  327. payload,
  328. value,
  329. gas,
  330. contract_function_no,
  331. ..
  332. } => {
  333. // When we generate an external call in codegen, we have already taken care of the
  334. // accounts we need (the payer for deploying creating the data accounts and the system
  335. // program).
  336. if *loc == Loc::Codegen {
  337. return;
  338. }
  339. let mut program_id_populated = false;
  340. if let Some(address) = address {
  341. address.recurse(data, check_expression);
  342. if let Expression::NumberLiteral { value, .. } = address {
  343. // Check if we can auto populate this account
  344. if let Some(account) = account_from_number(value) {
  345. data.add_account(
  346. account,
  347. &SolanaAccount {
  348. loc: Loc::Codegen,
  349. is_signer: false,
  350. is_writer: false,
  351. generated: true,
  352. },
  353. );
  354. program_id_populated = true;
  355. }
  356. }
  357. }
  358. if let Some(seeds) = seeds {
  359. seeds.recurse(data, check_expression);
  360. }
  361. payload.recurse(data, check_expression);
  362. value.recurse(data, check_expression);
  363. gas.recurse(data, check_expression);
  364. // External calls always need the system account
  365. data.add_system_account();
  366. if let Some(accounts) = accounts {
  367. accounts.recurse(data, check_expression);
  368. } else if let Some((contract_no, function_no)) = contract_function_no {
  369. transfer_accounts(loc, *contract_no, *function_no, data, program_id_populated);
  370. }
  371. }
  372. Instr::EmitEvent {
  373. data: data_,
  374. topics,
  375. ..
  376. } => {
  377. data_.recurse(data, check_expression);
  378. for item in topics {
  379. item.recurse(data, check_expression);
  380. }
  381. }
  382. Instr::Switch { cond, cases, .. } => {
  383. cond.recurse(data, check_expression);
  384. for (expr, _) in cases {
  385. expr.recurse(data, check_expression);
  386. }
  387. }
  388. Instr::AccountAccess { .. } => (),
  389. }
  390. }
  391. /// Collect accounts from this expression
  392. fn check_expression(expr: &Expression, data: &mut RecurseData) -> bool {
  393. match expr {
  394. Expression::Builtin {
  395. kind: Builtin::Timestamp | Builtin::BlockNumber | Builtin::Slot,
  396. ..
  397. } => {
  398. data.add_account(
  399. BuiltinAccounts::ClockAccount.to_string(),
  400. &SolanaAccount {
  401. loc: Loc::Codegen,
  402. is_signer: false,
  403. is_writer: false,
  404. generated: true,
  405. },
  406. );
  407. }
  408. Expression::Builtin {
  409. kind: Builtin::SignatureVerify,
  410. ..
  411. } => {
  412. data.add_account(
  413. BuiltinAccounts::InstructionAccount.to_string(),
  414. &SolanaAccount {
  415. loc: Loc::Codegen,
  416. is_writer: false,
  417. is_signer: false,
  418. generated: true,
  419. },
  420. );
  421. }
  422. Expression::Builtin {
  423. kind: Builtin::Ripemd160 | Builtin::Keccak256 | Builtin::Sha256,
  424. ..
  425. } => {
  426. data.add_system_account();
  427. }
  428. _ => (),
  429. }
  430. true
  431. }
  432. /// When we make an external call from function A to function B, function A must know all the
  433. /// accounts function B needs. The 'transfer_accounts' function takes care to transfer the accounts
  434. /// from B's IDL to A's IDL.
  435. fn transfer_accounts(
  436. loc: &pt::Loc,
  437. contract_no: usize,
  438. function_no: usize,
  439. data: &mut RecurseData,
  440. program_id_present: bool,
  441. ) {
  442. let accounts_to_add = data.functions[function_no].solana_accounts.borrow().clone();
  443. for (name, mut account) in accounts_to_add {
  444. if name == BuiltinAccounts::DataAccount {
  445. let idl_name = format!("{}_dataAccount", data.contracts[contract_no].name);
  446. if let Some(acc) = data.functions[data.ast_no]
  447. .solana_accounts
  448. .borrow()
  449. .get(&idl_name)
  450. {
  451. if acc.loc != *loc {
  452. data.diagnostics.push(
  453. Diagnostic::error_with_note(
  454. *loc,
  455. format!("contract '{}' is called more than once in this function, so automatic account collection cannot happen. \
  456. Please, provide the necessary accounts using the {{accounts:..}} call argument", data.contracts[contract_no].name),
  457. acc.loc,
  458. "other call".to_string(),
  459. )
  460. );
  461. }
  462. continue;
  463. }
  464. account.loc = *loc;
  465. data.add_account(idl_name, &account);
  466. continue;
  467. }
  468. if let Some(other_account) = data.functions[data.ast_no]
  469. .solana_accounts
  470. .borrow()
  471. .get(&name)
  472. {
  473. if !other_account.generated {
  474. data.diagnostics.push(
  475. Diagnostic::error_with_note(
  476. other_account.loc,
  477. "account name collision encountered. Calling a function that \
  478. requires an account whose name is also defined in the current function \
  479. will create duplicate names in the IDL. Please, rename one of the accounts".to_string(),
  480. account.loc,
  481. "other declaration".to_string(),
  482. )
  483. );
  484. }
  485. }
  486. data.add_account(name, &account);
  487. }
  488. if !program_id_present {
  489. data.functions[data.ast_no]
  490. .solana_accounts
  491. .borrow_mut()
  492. .insert(
  493. format!("{}_programId", data.contracts[contract_no].name),
  494. SolanaAccount {
  495. is_signer: false,
  496. is_writer: false,
  497. generated: true,
  498. loc: *loc,
  499. },
  500. );
  501. }
  502. let cfg_no = data.contracts[contract_no].all_functions[&function_no];
  503. data.next_queue.insert((contract_no, cfg_no));
  504. data.next_queue.insert((data.contract_no, data.cfg_func_no));
  505. }