1use serde_json::Value;
2use std::collections::HashMap;
3use tower_lsp::lsp_types::{
4 CompletionItem, CompletionItemKind, CompletionList, CompletionResponse, Position,
5};
6
7use crate::goto::CHILD_KEYS;
8
9#[derive(Debug, Clone)]
11pub struct ScopedDeclaration {
12 pub name: String,
14 pub type_id: String,
16}
17
18#[derive(Debug, Clone)]
20pub struct ScopeRange {
21 pub node_id: u64,
23 pub start: usize,
25 pub end: usize,
27 pub file_id: u64,
29}
30
31pub struct CompletionCache {
33 pub names: Vec<CompletionItem>,
35
36 pub name_to_type: HashMap<String, String>,
38
39 pub node_members: HashMap<u64, Vec<CompletionItem>>,
41
42 pub type_to_node: HashMap<String, u64>,
44
45 pub name_to_node_id: HashMap<String, u64>,
47
48 pub method_identifiers: HashMap<u64, Vec<CompletionItem>>,
51
52 pub function_return_types: HashMap<(u64, String), String>,
55
56 pub using_for: HashMap<String, Vec<CompletionItem>>,
59
60 pub using_for_wildcard: Vec<CompletionItem>,
62
63 pub general_completions: Vec<CompletionItem>,
66
67 pub scope_declarations: HashMap<u64, Vec<ScopedDeclaration>>,
71
72 pub scope_parent: HashMap<u64, u64>,
75
76 pub scope_ranges: Vec<ScopeRange>,
79
80 pub path_to_file_id: HashMap<String, u64>,
83
84 pub linearized_base_contracts: HashMap<u64, Vec<u64>>,
88
89 pub contract_kinds: HashMap<u64, String>,
93}
94
95fn push_if_node_or_array<'a>(tree: &'a Value, key: &str, stack: &mut Vec<&'a Value>) {
96 if let Some(value) = tree.get(key) {
97 match value {
98 Value::Array(arr) => stack.extend(arr),
99 Value::Object(_) => stack.push(value),
100 _ => {}
101 }
102 }
103}
104
105fn node_type_to_completion_kind(node_type: &str) -> CompletionItemKind {
107 match node_type {
108 "FunctionDefinition" => CompletionItemKind::FUNCTION,
109 "VariableDeclaration" => CompletionItemKind::VARIABLE,
110 "ContractDefinition" => CompletionItemKind::CLASS,
111 "StructDefinition" => CompletionItemKind::STRUCT,
112 "EnumDefinition" => CompletionItemKind::ENUM,
113 "EnumValue" => CompletionItemKind::ENUM_MEMBER,
114 "EventDefinition" => CompletionItemKind::EVENT,
115 "ErrorDefinition" => CompletionItemKind::EVENT,
116 "ModifierDefinition" => CompletionItemKind::METHOD,
117 "ImportDirective" => CompletionItemKind::MODULE,
118 _ => CompletionItemKind::TEXT,
119 }
120}
121
122fn parse_src(node: &Value) -> Option<(usize, usize, u64)> {
125 let src = node.get("src").and_then(|v| v.as_str())?;
126 let mut parts = src.split(':');
127 let offset = parts.next()?.parse::<usize>().ok()?;
128 let length = parts.next()?.parse::<usize>().ok()?;
129 let file_id = parts.next()?.parse::<u64>().ok()?;
130 Some((offset, length, file_id))
131}
132
133pub fn extract_node_id_from_type(type_id: &str) -> Option<u64> {
138 let mut last_id = None;
141 let mut i = 0;
142 let bytes = type_id.as_bytes();
143 while i < bytes.len() {
144 if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
145 i += 2;
146 let start = i;
147 while i < bytes.len() && bytes[i].is_ascii_digit() {
148 i += 1;
149 }
150 if i > start
151 && let Ok(id) = type_id[start..i].parse::<u64>()
152 {
153 last_id = Some(id);
154 }
155 } else {
156 i += 1;
157 }
158 }
159 last_id
160}
161
162fn build_function_signature(node: &Value) -> Option<String> {
165 let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("");
166 if name.is_empty() {
167 return None;
168 }
169
170 let params = node
171 .get("parameters")
172 .and_then(|p| p.get("parameters"))
173 .and_then(|v| v.as_array());
174
175 let mut sig = String::new();
176 sig.push_str(name);
177 sig.push('(');
178
179 if let Some(params) = params {
180 for (i, param) in params.iter().enumerate() {
181 if i > 0 {
182 sig.push_str(", ");
183 }
184 let type_str = param
185 .get("typeDescriptions")
186 .and_then(|td| td.get("typeString"))
187 .and_then(|v| v.as_str())
188 .unwrap_or("?");
189 let clean_type = type_str
191 .strip_prefix("struct ")
192 .or_else(|| type_str.strip_prefix("contract "))
193 .or_else(|| type_str.strip_prefix("enum "))
194 .unwrap_or(type_str);
195 let param_name = param.get("name").and_then(|v| v.as_str()).unwrap_or("");
196 sig.push_str(clean_type);
197 if !param_name.is_empty() {
198 sig.push(' ');
199 sig.push_str(param_name);
200 }
201 }
202 }
203 sig.push(')');
204
205 let returns = node
207 .get("returnParameters")
208 .and_then(|p| p.get("parameters"))
209 .and_then(|v| v.as_array());
210
211 if let Some(returns) = returns
212 && !returns.is_empty()
213 {
214 sig.push_str(" returns (");
215 for (i, ret) in returns.iter().enumerate() {
216 if i > 0 {
217 sig.push_str(", ");
218 }
219 let type_str = ret
220 .get("typeDescriptions")
221 .and_then(|td| td.get("typeString"))
222 .and_then(|v| v.as_str())
223 .unwrap_or("?");
224 let clean_type = type_str
225 .strip_prefix("struct ")
226 .or_else(|| type_str.strip_prefix("contract "))
227 .or_else(|| type_str.strip_prefix("enum "))
228 .unwrap_or(type_str);
229 let ret_name = ret.get("name").and_then(|v| v.as_str()).unwrap_or("");
230 sig.push_str(clean_type);
231 if !ret_name.is_empty() {
232 sig.push(' ');
233 sig.push_str(ret_name);
234 }
235 }
236 sig.push(')');
237 }
238
239 Some(sig)
240}
241
242pub fn extract_mapping_value_type(type_id: &str) -> Option<String> {
249 let mut current = type_id;
250
251 loop {
252 if !current.starts_with("t_mapping$_") {
253 let result = current.trim_end_matches("_$");
256 return if result.is_empty() {
257 None
258 } else {
259 Some(result.to_string())
260 };
261 }
262
263 let inner = ¤t["t_mapping$_".len()..];
265
266 let mut depth = 0i32;
270 let bytes = inner.as_bytes();
271 let mut split_pos = None;
272
273 let mut i = 0;
274 while i < bytes.len() {
275 if i + 1 < bytes.len() && bytes[i] == b'$' && bytes[i + 1] == b'_' {
276 depth += 1;
277 i += 2;
278 } else if i + 2 < bytes.len()
279 && bytes[i] == b'_'
280 && bytes[i + 1] == b'$'
281 && bytes[i + 2] == b'_'
282 && depth == 0
283 {
284 split_pos = Some(i);
286 break;
287 } else if i + 1 < bytes.len() && bytes[i] == b'_' && bytes[i + 1] == b'$' {
288 depth -= 1;
289 i += 2;
290 } else {
291 i += 1;
292 }
293 }
294
295 if let Some(pos) = split_pos {
296 current = &inner[pos + 3..];
298 } else {
299 return None;
300 }
301 }
302}
303
304fn count_abi_params(signature: &str) -> usize {
307 let start = match signature.find('(') {
309 Some(i) => i + 1,
310 None => return 0,
311 };
312 let bytes = signature.as_bytes();
313 if start >= bytes.len() {
314 return 0;
315 }
316 if bytes[start] == b')' {
318 return 0;
319 }
320 let mut count = 1; let mut depth = 0;
322 for &b in &bytes[start..] {
323 match b {
324 b'(' => depth += 1,
325 b')' => {
326 if depth == 0 {
327 break;
328 }
329 depth -= 1;
330 }
331 b',' if depth == 0 => count += 1,
332 _ => {}
333 }
334 }
335 count
336}
337
338fn count_signature_params(sig: &str) -> usize {
340 count_abi_params(sig)
341}
342
343pub fn build_completion_cache(sources: &Value, contracts: Option<&Value>) -> CompletionCache {
346 let mut names: Vec<CompletionItem> = Vec::new();
347 let mut seen_names: HashMap<String, usize> = HashMap::new(); let mut name_to_type: HashMap<String, String> = HashMap::new();
349 let mut node_members: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
350 let mut type_to_node: HashMap<String, u64> = HashMap::new();
351 let mut method_identifiers: HashMap<u64, Vec<CompletionItem>> = HashMap::new();
352 let mut name_to_node_id: HashMap<String, u64> = HashMap::new();
353 let mut contract_kinds: HashMap<u64, String> = HashMap::new();
354
355 let mut contract_locations: Vec<(String, String, u64)> = Vec::new();
357
358 let mut function_signatures: HashMap<u64, HashMap<String, Vec<String>>> = HashMap::new();
360
361 let mut function_return_types: HashMap<(u64, String), String> = HashMap::new();
363
364 let mut using_for: HashMap<String, Vec<CompletionItem>> = HashMap::new();
366 let mut using_for_wildcard: Vec<CompletionItem> = Vec::new();
367
368 let mut using_for_directives: Vec<(u64, Option<String>)> = Vec::new();
370
371 let mut scope_declarations: HashMap<u64, Vec<ScopedDeclaration>> = HashMap::new();
373 let mut scope_parent: HashMap<u64, u64> = HashMap::new();
374 let mut scope_ranges: Vec<ScopeRange> = Vec::new();
375 let mut path_to_file_id: HashMap<String, u64> = HashMap::new();
376 let mut linearized_base_contracts: HashMap<u64, Vec<u64>> = HashMap::new();
377
378 if let Some(sources_obj) = sources.as_object() {
379 for (path, contents) in sources_obj {
380 if let Some(contents_array) = contents.as_array()
381 && let Some(first_content) = contents_array.first()
382 && let Some(source_file) = first_content.get("source_file")
383 && let Some(ast) = source_file.get("ast")
384 {
385 if let Some(fid) = source_file.get("id").and_then(|v| v.as_u64()) {
387 path_to_file_id.insert(path.clone(), fid);
388 }
389 let mut stack: Vec<&Value> = vec![ast];
390
391 while let Some(tree) = stack.pop() {
392 let node_type = tree.get("nodeType").and_then(|v| v.as_str()).unwrap_or("");
393 let name = tree.get("name").and_then(|v| v.as_str()).unwrap_or("");
394 let node_id = tree.get("id").and_then(|v| v.as_u64());
395
396 let is_scope_node = matches!(
401 node_type,
402 "SourceUnit"
403 | "ContractDefinition"
404 | "FunctionDefinition"
405 | "ModifierDefinition"
406 | "Block"
407 | "UncheckedBlock"
408 );
409 if is_scope_node && let Some(nid) = node_id {
410 if let Some((start, len, file_id)) = parse_src(tree) {
411 scope_ranges.push(ScopeRange {
412 node_id: nid,
413 start,
414 end: start + len,
415 file_id,
416 });
417 }
418 if let Some(parent_id) = tree.get("scope").and_then(|v| v.as_u64()) {
420 scope_parent.insert(nid, parent_id);
421 }
422 }
423
424 if node_type == "ContractDefinition"
426 && let Some(nid) = node_id
427 && let Some(bases) = tree
428 .get("linearizedBaseContracts")
429 .and_then(|v| v.as_array())
430 {
431 let base_ids: Vec<u64> = bases.iter().filter_map(|b| b.as_u64()).collect();
432 if !base_ids.is_empty() {
433 linearized_base_contracts.insert(nid, base_ids);
434 }
435 }
436
437 if node_type == "VariableDeclaration"
439 && !name.is_empty()
440 && let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64())
441 && let Some(tid) = tree
442 .get("typeDescriptions")
443 .and_then(|td| td.get("typeIdentifier"))
444 .and_then(|v| v.as_str())
445 {
446 scope_declarations
447 .entry(scope_id)
448 .or_default()
449 .push(ScopedDeclaration {
450 name: name.to_string(),
451 type_id: tid.to_string(),
452 });
453 }
454
455 if node_type == "FunctionDefinition"
457 && !name.is_empty()
458 && let Some(scope_id) = tree.get("scope").and_then(|v| v.as_u64())
459 && let Some(tid) = tree
460 .get("typeDescriptions")
461 .and_then(|td| td.get("typeIdentifier"))
462 .and_then(|v| v.as_str())
463 {
464 scope_declarations
465 .entry(scope_id)
466 .or_default()
467 .push(ScopedDeclaration {
468 name: name.to_string(),
469 type_id: tid.to_string(),
470 });
471 }
472
473 if !name.is_empty() && !seen_names.contains_key(name) {
475 let type_string = tree
476 .get("typeDescriptions")
477 .and_then(|td| td.get("typeString"))
478 .and_then(|v| v.as_str())
479 .map(|s| s.to_string());
480
481 let type_id = tree
482 .get("typeDescriptions")
483 .and_then(|td| td.get("typeIdentifier"))
484 .and_then(|v| v.as_str());
485
486 let kind = node_type_to_completion_kind(node_type);
487
488 let item = CompletionItem {
489 label: name.to_string(),
490 kind: Some(kind),
491 detail: type_string,
492 ..Default::default()
493 };
494
495 let idx = names.len();
496 names.push(item);
497 seen_names.insert(name.to_string(), idx);
498
499 if let Some(tid) = type_id {
501 name_to_type.insert(name.to_string(), tid.to_string());
502 }
503 }
504
505 if node_type == "StructDefinition"
507 && let Some(id) = node_id
508 {
509 let mut members = Vec::new();
510 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
511 for member in member_array {
512 let member_name =
513 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
514 if member_name.is_empty() {
515 continue;
516 }
517 let member_type = member
518 .get("typeDescriptions")
519 .and_then(|td| td.get("typeString"))
520 .and_then(|v| v.as_str())
521 .map(|s| s.to_string());
522
523 members.push(CompletionItem {
524 label: member_name.to_string(),
525 kind: Some(CompletionItemKind::FIELD),
526 detail: member_type,
527 ..Default::default()
528 });
529 }
530 }
531 if !members.is_empty() {
532 node_members.insert(id, members);
533 }
534
535 if let Some(tid) = tree
537 .get("typeDescriptions")
538 .and_then(|td| td.get("typeIdentifier"))
539 .and_then(|v| v.as_str())
540 {
541 type_to_node.insert(tid.to_string(), id);
542 }
543 }
544
545 if node_type == "ContractDefinition"
547 && let Some(id) = node_id
548 {
549 let mut members = Vec::new();
550 let mut fn_sigs: HashMap<String, Vec<String>> = HashMap::new();
551 if let Some(nodes_array) = tree.get("nodes").and_then(|v| v.as_array()) {
552 for member in nodes_array {
553 let member_type = member
554 .get("nodeType")
555 .and_then(|v| v.as_str())
556 .unwrap_or("");
557 let member_name =
558 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
559 if member_name.is_empty() {
560 continue;
561 }
562
563 let (member_detail, label_details) =
565 if member_type == "FunctionDefinition" {
566 if let Some(ret_params) = member
570 .get("returnParameters")
571 .and_then(|rp| rp.get("parameters"))
572 .and_then(|v| v.as_array())
573 && ret_params.len() == 1
574 && let Some(ret_tid) = ret_params[0]
575 .get("typeDescriptions")
576 .and_then(|td| td.get("typeIdentifier"))
577 .and_then(|v| v.as_str())
578 {
579 function_return_types.insert(
580 (id, member_name.to_string()),
581 ret_tid.to_string(),
582 );
583 }
584
585 if let Some(sig) = build_function_signature(member) {
586 fn_sigs
587 .entry(member_name.to_string())
588 .or_default()
589 .push(sig.clone());
590 (Some(sig), None)
591 } else {
592 (
593 member
594 .get("typeDescriptions")
595 .and_then(|td| td.get("typeString"))
596 .and_then(|v| v.as_str())
597 .map(|s| s.to_string()),
598 None,
599 )
600 }
601 } else {
602 (
603 member
604 .get("typeDescriptions")
605 .and_then(|td| td.get("typeString"))
606 .and_then(|v| v.as_str())
607 .map(|s| s.to_string()),
608 None,
609 )
610 };
611
612 let kind = node_type_to_completion_kind(member_type);
613 members.push(CompletionItem {
614 label: member_name.to_string(),
615 kind: Some(kind),
616 detail: member_detail,
617 label_details,
618 ..Default::default()
619 });
620 }
621 }
622 if !members.is_empty() {
623 node_members.insert(id, members);
624 }
625 if !fn_sigs.is_empty() {
626 function_signatures.insert(id, fn_sigs);
627 }
628
629 if let Some(tid) = tree
630 .get("typeDescriptions")
631 .and_then(|td| td.get("typeIdentifier"))
632 .and_then(|v| v.as_str())
633 {
634 type_to_node.insert(tid.to_string(), id);
635 }
636
637 if !name.is_empty() {
639 contract_locations.push((path.clone(), name.to_string(), id));
640 name_to_node_id.insert(name.to_string(), id);
641 }
642
643 if let Some(ck) = tree.get("contractKind").and_then(|v| v.as_str()) {
645 contract_kinds.insert(id, ck.to_string());
646 }
647 }
648
649 if node_type == "EnumDefinition"
651 && let Some(id) = node_id
652 {
653 let mut members = Vec::new();
654 if let Some(member_array) = tree.get("members").and_then(|v| v.as_array()) {
655 for member in member_array {
656 let member_name =
657 member.get("name").and_then(|v| v.as_str()).unwrap_or("");
658 if member_name.is_empty() {
659 continue;
660 }
661 members.push(CompletionItem {
662 label: member_name.to_string(),
663 kind: Some(CompletionItemKind::ENUM_MEMBER),
664 detail: None,
665 ..Default::default()
666 });
667 }
668 }
669 if !members.is_empty() {
670 node_members.insert(id, members);
671 }
672
673 if let Some(tid) = tree
674 .get("typeDescriptions")
675 .and_then(|td| td.get("typeIdentifier"))
676 .and_then(|v| v.as_str())
677 {
678 type_to_node.insert(tid.to_string(), id);
679 }
680 }
681
682 if node_type == "UsingForDirective" {
684 let target_type = tree.get("typeName").and_then(|tn| {
686 tn.get("typeDescriptions")
687 .and_then(|td| td.get("typeIdentifier"))
688 .and_then(|v| v.as_str())
689 .map(|s| s.to_string())
690 });
691
692 if let Some(lib) = tree.get("libraryName") {
694 if let Some(lib_id) =
695 lib.get("referencedDeclaration").and_then(|v| v.as_u64())
696 {
697 using_for_directives.push((lib_id, target_type));
698 }
699 }
700 else if let Some(func_list) =
704 tree.get("functionList").and_then(|v| v.as_array())
705 {
706 for entry in func_list {
707 if entry.get("operator").is_some() {
709 continue;
710 }
711 if let Some(def) = entry.get("definition") {
712 let fn_name =
713 def.get("name").and_then(|v| v.as_str()).unwrap_or("");
714 if !fn_name.is_empty() {
715 let items = if let Some(ref tid) = target_type {
716 using_for.entry(tid.clone()).or_default()
717 } else {
718 &mut using_for_wildcard
719 };
720 items.push(CompletionItem {
721 label: fn_name.to_string(),
722 kind: Some(CompletionItemKind::FUNCTION),
723 detail: None,
724 ..Default::default()
725 });
726 }
727 }
728 }
729 }
730 }
731
732 for key in CHILD_KEYS {
734 push_if_node_or_array(tree, key, &mut stack);
735 }
736 }
737 }
738 }
739 }
740
741 for (lib_id, target_type) in &using_for_directives {
744 if let Some(lib_members) = node_members.get(lib_id) {
745 let items: Vec<CompletionItem> = lib_members
746 .iter()
747 .filter(|item| item.kind == Some(CompletionItemKind::FUNCTION))
748 .cloned()
749 .collect();
750 if !items.is_empty() {
751 if let Some(tid) = target_type {
752 using_for.entry(tid.clone()).or_default().extend(items);
753 } else {
754 using_for_wildcard.extend(items);
755 }
756 }
757 }
758 }
759
760 if let Some(contracts_val) = contracts
762 && let Some(contracts_obj) = contracts_val.as_object()
763 {
764 for (path, contract_name, node_id) in &contract_locations {
765 let fn_sigs = function_signatures.get(node_id);
767
768 if let Some(path_entry) = contracts_obj.get(path)
769 && let Some(contract_entry) = path_entry.get(contract_name)
770 && let Some(first) = contract_entry.get(0)
771 && let Some(evm) = first.get("contract").and_then(|c| c.get("evm"))
772 && let Some(methods) = evm.get("methodIdentifiers")
773 && let Some(methods_obj) = methods.as_object()
774 {
775 let mut items: Vec<CompletionItem> = Vec::new();
776 for (signature, selector) in methods_obj {
777 let fn_name = signature.split('(').next().unwrap_or(signature).to_string();
780 let selector_str = selector
781 .as_str()
782 .map(|s| format!("0x{}", s))
783 .unwrap_or_default();
784
785 let description =
787 fn_sigs
788 .and_then(|sigs| sigs.get(&fn_name))
789 .and_then(|sig_list| {
790 if sig_list.len() == 1 {
791 Some(sig_list[0].clone())
793 } else {
794 let abi_param_count = count_abi_params(signature);
796 sig_list
797 .iter()
798 .find(|s| count_signature_params(s) == abi_param_count)
799 .cloned()
800 }
801 });
802
803 items.push(CompletionItem {
804 label: fn_name,
805 kind: Some(CompletionItemKind::FUNCTION),
806 detail: Some(signature.clone()),
807 label_details: Some(tower_lsp::lsp_types::CompletionItemLabelDetails {
808 detail: Some(selector_str),
809 description,
810 }),
811 ..Default::default()
812 });
813 }
814 if !items.is_empty() {
815 method_identifiers.insert(*node_id, items);
816 }
817 }
818 }
819 }
820
821 let mut general_completions = names.clone();
823 general_completions.extend(get_static_completions());
824
825 scope_ranges.sort_by_key(|r| r.end - r.start);
827
828 let orphan_ids: Vec<u64> = scope_ranges
834 .iter()
835 .filter(|r| !scope_parent.contains_key(&r.node_id))
836 .map(|r| r.node_id)
837 .collect();
838 let range_by_id: HashMap<u64, (usize, usize, u64)> = scope_ranges
840 .iter()
841 .map(|r| (r.node_id, (r.start, r.end, r.file_id)))
842 .collect();
843 for orphan_id in &orphan_ids {
844 if let Some(&(start, end, file_id)) = range_by_id.get(orphan_id) {
845 let parent = scope_ranges
848 .iter()
849 .find(|r| {
850 r.node_id != *orphan_id
851 && r.file_id == file_id
852 && r.start <= start
853 && r.end >= end
854 && (r.end - r.start) > (end - start)
855 })
856 .map(|r| r.node_id);
857 if let Some(parent_id) = parent {
858 scope_parent.insert(*orphan_id, parent_id);
859 }
860 }
861 }
862
863 CompletionCache {
864 names,
865 name_to_type,
866 node_members,
867 type_to_node,
868 name_to_node_id,
869 method_identifiers,
870 function_return_types,
871 using_for,
872 using_for_wildcard,
873 general_completions,
874 scope_declarations,
875 scope_parent,
876 scope_ranges,
877 path_to_file_id,
878 linearized_base_contracts,
879 contract_kinds,
880 }
881}
882
883fn magic_members(name: &str) -> Option<Vec<CompletionItem>> {
885 let items = match name {
886 "msg" => vec![
887 ("data", "bytes calldata"),
888 ("sender", "address"),
889 ("sig", "bytes4"),
890 ("value", "uint256"),
891 ],
892 "block" => vec![
893 ("basefee", "uint256"),
894 ("blobbasefee", "uint256"),
895 ("chainid", "uint256"),
896 ("coinbase", "address payable"),
897 ("difficulty", "uint256"),
898 ("gaslimit", "uint256"),
899 ("number", "uint256"),
900 ("prevrandao", "uint256"),
901 ("timestamp", "uint256"),
902 ],
903 "tx" => vec![("gasprice", "uint256"), ("origin", "address")],
904 "abi" => vec![
905 ("decode(bytes memory, (...))", "..."),
906 ("encode(...)", "bytes memory"),
907 ("encodePacked(...)", "bytes memory"),
908 ("encodeWithSelector(bytes4, ...)", "bytes memory"),
909 ("encodeWithSignature(string memory, ...)", "bytes memory"),
910 ("encodeCall(function, (...))", "bytes memory"),
911 ],
912 "type" => vec![
915 ("name", "string"),
916 ("creationCode", "bytes memory"),
917 ("runtimeCode", "bytes memory"),
918 ("interfaceId", "bytes4"),
919 ("min", "T"),
920 ("max", "T"),
921 ],
922 "bytes" => vec![("concat(...)", "bytes memory")],
924 "string" => vec![("concat(...)", "string memory")],
925 _ => return None,
926 };
927
928 Some(
929 items
930 .into_iter()
931 .map(|(label, detail)| CompletionItem {
932 label: label.to_string(),
933 kind: Some(CompletionItemKind::PROPERTY),
934 detail: Some(detail.to_string()),
935 ..Default::default()
936 })
937 .collect(),
938 )
939}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq)]
944enum TypeMetaKind {
945 Contract,
947 Interface,
949 IntegerType,
951 Unknown,
953}
954
955fn classify_type_arg(arg: &str, cache: Option<&CompletionCache>) -> TypeMetaKind {
957 if arg == "int" || arg == "uint" {
959 return TypeMetaKind::IntegerType;
960 }
961 if let Some(suffix) = arg.strip_prefix("uint").or_else(|| arg.strip_prefix("int"))
962 && let Ok(n) = suffix.parse::<u16>()
963 && (8..=256).contains(&n)
964 && n % 8 == 0
965 {
966 return TypeMetaKind::IntegerType;
967 }
968
969 if let Some(c) = cache
971 && let Some(&node_id) = c.name_to_node_id.get(arg)
972 {
973 return match c.contract_kinds.get(&node_id).map(|s| s.as_str()) {
974 Some("interface") => TypeMetaKind::Interface,
975 Some("library") => TypeMetaKind::Contract, _ => TypeMetaKind::Contract,
977 };
978 }
979
980 TypeMetaKind::Unknown
981}
982
983fn type_meta_members(arg: Option<&str>, cache: Option<&CompletionCache>) -> Vec<CompletionItem> {
985 let kind = match arg {
986 Some(a) => classify_type_arg(a, cache),
987 None => TypeMetaKind::Unknown,
988 };
989
990 let items: Vec<(&str, &str)> = match kind {
991 TypeMetaKind::Contract => vec![
992 ("name", "string"),
993 ("creationCode", "bytes memory"),
994 ("runtimeCode", "bytes memory"),
995 ],
996 TypeMetaKind::Interface => vec![("name", "string"), ("interfaceId", "bytes4")],
997 TypeMetaKind::IntegerType => vec![("min", "T"), ("max", "T")],
998 TypeMetaKind::Unknown => vec![
999 ("name", "string"),
1000 ("creationCode", "bytes memory"),
1001 ("runtimeCode", "bytes memory"),
1002 ("interfaceId", "bytes4"),
1003 ("min", "T"),
1004 ("max", "T"),
1005 ],
1006 };
1007
1008 items
1009 .into_iter()
1010 .map(|(label, detail)| CompletionItem {
1011 label: label.to_string(),
1012 kind: Some(CompletionItemKind::PROPERTY),
1013 detail: Some(detail.to_string()),
1014 ..Default::default()
1015 })
1016 .collect()
1017}
1018
1019fn address_members() -> Vec<CompletionItem> {
1021 [
1022 ("balance", "uint256", CompletionItemKind::PROPERTY),
1023 ("code", "bytes memory", CompletionItemKind::PROPERTY),
1024 ("codehash", "bytes32", CompletionItemKind::PROPERTY),
1025 ("transfer(uint256)", "", CompletionItemKind::FUNCTION),
1026 ("send(uint256)", "bool", CompletionItemKind::FUNCTION),
1027 (
1028 "call(bytes memory)",
1029 "(bool, bytes memory)",
1030 CompletionItemKind::FUNCTION,
1031 ),
1032 (
1033 "delegatecall(bytes memory)",
1034 "(bool, bytes memory)",
1035 CompletionItemKind::FUNCTION,
1036 ),
1037 (
1038 "staticcall(bytes memory)",
1039 "(bool, bytes memory)",
1040 CompletionItemKind::FUNCTION,
1041 ),
1042 ]
1043 .iter()
1044 .map(|(label, detail, kind)| CompletionItem {
1045 label: label.to_string(),
1046 kind: Some(*kind),
1047 detail: if detail.is_empty() {
1048 None
1049 } else {
1050 Some(detail.to_string())
1051 },
1052 ..Default::default()
1053 })
1054 .collect()
1055}
1056
1057#[derive(Debug, Clone, PartialEq)]
1059pub enum AccessKind {
1060 Plain,
1062 Call,
1064 Index,
1066}
1067
1068#[derive(Debug, Clone, PartialEq)]
1070pub struct DotSegment {
1071 pub name: String,
1072 pub kind: AccessKind,
1073 pub call_args: Option<String>,
1076}
1077
1078fn skip_brackets_backwards(bytes: &[u8], pos: usize) -> usize {
1082 let close = bytes[pos];
1083 let open = match close {
1084 b')' => b'(',
1085 b']' => b'[',
1086 _ => return pos,
1087 };
1088 let mut depth = 1u32;
1089 let mut i = pos;
1090 while i > 0 && depth > 0 {
1091 i -= 1;
1092 if bytes[i] == close {
1093 depth += 1;
1094 } else if bytes[i] == open {
1095 depth -= 1;
1096 }
1097 }
1098 i
1099}
1100
1101pub fn parse_dot_chain(line: &str, character: u32) -> Vec<DotSegment> {
1106 let col = character as usize;
1107 if col == 0 {
1108 return vec![];
1109 }
1110
1111 let bytes = line.as_bytes();
1112 let mut segments: Vec<DotSegment> = Vec::new();
1113
1114 let mut pos = col;
1116 if pos > 0 && pos <= bytes.len() && bytes[pos - 1] == b'.' {
1117 pos -= 1;
1118 }
1119
1120 loop {
1121 if pos == 0 {
1122 break;
1123 }
1124
1125 let (kind, call_args) = if bytes[pos - 1] == b')' {
1127 let close = pos - 1; pos = skip_brackets_backwards(bytes, close);
1129 let args_text = String::from_utf8_lossy(&bytes[pos + 1..close])
1131 .trim()
1132 .to_string();
1133 let args = if args_text.is_empty() {
1134 None
1135 } else {
1136 Some(args_text)
1137 };
1138 (AccessKind::Call, args)
1139 } else if bytes[pos - 1] == b']' {
1140 pos -= 1; pos = skip_brackets_backwards(bytes, pos);
1142 (AccessKind::Index, None)
1143 } else {
1144 (AccessKind::Plain, None)
1145 };
1146
1147 let end = pos;
1149 while pos > 0 && (bytes[pos - 1].is_ascii_alphanumeric() || bytes[pos - 1] == b'_') {
1150 pos -= 1;
1151 }
1152
1153 if pos == end {
1154 break;
1156 }
1157
1158 let name = String::from_utf8_lossy(&bytes[pos..end]).to_string();
1159 segments.push(DotSegment {
1160 name,
1161 kind,
1162 call_args,
1163 });
1164
1165 if pos > 0 && bytes[pos - 1] == b'.' {
1167 pos -= 1; } else {
1169 break;
1170 }
1171 }
1172
1173 segments.reverse(); segments
1175}
1176
1177pub fn extract_identifier_before_dot(line: &str, character: u32) -> Option<String> {
1180 let segments = parse_dot_chain(line, character);
1181 segments.last().map(|s| s.name.clone())
1182}
1183
1184#[doc = r"Strip all storage/memory location suffixes from a typeIdentifier to get the base type.
1185Solidity AST uses different suffixes in different contexts:
1186 - `t_struct$_State_$4809_storage_ptr` (UsingForDirective typeName)
1187 - `t_struct$_State_$4809_storage` (mapping value type after extraction)
1188 - `t_struct$_PoolKey_$8887_memory_ptr` (function parameter)
1189All refer to the same logical type. This strips `_ptr` and `_storage`/`_memory`/`_calldata`."]
1190fn strip_type_suffix(type_id: &str) -> &str {
1191 let s = type_id.strip_suffix("_ptr").unwrap_or(type_id);
1192 s.strip_suffix("_storage")
1193 .or_else(|| s.strip_suffix("_memory"))
1194 .or_else(|| s.strip_suffix("_calldata"))
1195 .unwrap_or(s)
1196}
1197
1198fn lookup_using_for(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1202 if let Some(items) = cache.using_for.get(type_id) {
1204 return items.clone();
1205 }
1206
1207 let base = strip_type_suffix(type_id);
1209 let variants = [
1210 base.to_string(),
1211 format!("{}_storage", base),
1212 format!("{}_storage_ptr", base),
1213 format!("{}_memory", base),
1214 format!("{}_memory_ptr", base),
1215 format!("{}_calldata", base),
1216 ];
1217 for variant in &variants {
1218 if variant.as_str() != type_id
1219 && let Some(items) = cache.using_for.get(variant.as_str())
1220 {
1221 return items.clone();
1222 }
1223 }
1224
1225 vec![]
1226}
1227
1228fn completions_for_type(cache: &CompletionCache, type_id: &str) -> Vec<CompletionItem> {
1231 if type_id == "t_address" || type_id == "t_address_payable" {
1233 let mut items = address_members();
1234 if let Some(uf) = cache.using_for.get(type_id) {
1236 items.extend(uf.iter().cloned());
1237 }
1238 items.extend(cache.using_for_wildcard.iter().cloned());
1239 return items;
1240 }
1241
1242 let resolved_node_id = extract_node_id_from_type(type_id)
1243 .or_else(|| cache.type_to_node.get(type_id).copied())
1244 .or_else(|| {
1245 type_id
1247 .strip_prefix("__node_id_")
1248 .and_then(|s| s.parse::<u64>().ok())
1249 });
1250
1251 let mut items = Vec::new();
1252 let mut seen_labels: std::collections::HashSet<String> = std::collections::HashSet::new();
1253
1254 if let Some(node_id) = resolved_node_id {
1255 if let Some(method_items) = cache.method_identifiers.get(&node_id) {
1257 for item in method_items {
1258 seen_labels.insert(item.label.clone());
1259 items.push(item.clone());
1260 }
1261 }
1262
1263 if let Some(members) = cache.node_members.get(&node_id) {
1265 for item in members {
1266 if !seen_labels.contains(&item.label) {
1267 seen_labels.insert(item.label.clone());
1268 items.push(item.clone());
1269 }
1270 }
1271 }
1272 }
1273
1274 let is_contract_name = resolved_node_id
1278 .map(|nid| cache.contract_kinds.contains_key(&nid))
1279 .unwrap_or(false);
1280
1281 if !is_contract_name {
1282 let uf_items = lookup_using_for(cache, type_id);
1284 for item in &uf_items {
1285 if !seen_labels.contains(&item.label) {
1286 seen_labels.insert(item.label.clone());
1287 items.push(item.clone());
1288 }
1289 }
1290
1291 for item in &cache.using_for_wildcard {
1293 if !seen_labels.contains(&item.label) {
1294 seen_labels.insert(item.label.clone());
1295 items.push(item.clone());
1296 }
1297 }
1298 }
1299
1300 items
1301}
1302
1303fn resolve_name_to_type_id(cache: &CompletionCache, name: &str) -> Option<String> {
1305 if let Some(tid) = cache.name_to_type.get(name) {
1307 return Some(tid.clone());
1308 }
1309 if let Some(node_id) = cache.name_to_node_id.get(name) {
1311 for (tid, nid) in &cache.type_to_node {
1313 if nid == node_id {
1314 return Some(tid.clone());
1315 }
1316 }
1317 return Some(format!("__node_id_{}", node_id));
1319 }
1320 None
1321}
1322
1323pub fn find_innermost_scope(cache: &CompletionCache, byte_pos: usize, file_id: u64) -> Option<u64> {
1327 cache
1329 .scope_ranges
1330 .iter()
1331 .find(|r| r.file_id == file_id && r.start <= byte_pos && byte_pos < r.end)
1332 .map(|r| r.node_id)
1333}
1334
1335pub fn resolve_name_in_scope(
1344 cache: &CompletionCache,
1345 name: &str,
1346 byte_pos: usize,
1347 file_id: u64,
1348) -> Option<String> {
1349 let mut current_scope = find_innermost_scope(cache, byte_pos, file_id)?;
1350
1351 loop {
1353 if let Some(decls) = cache.scope_declarations.get(¤t_scope) {
1355 for decl in decls {
1356 if decl.name == name {
1357 return Some(decl.type_id.clone());
1358 }
1359 }
1360 }
1361
1362 if let Some(bases) = cache.linearized_base_contracts.get(¤t_scope) {
1366 for &base_id in bases.iter().skip(1) {
1367 if let Some(decls) = cache.scope_declarations.get(&base_id) {
1368 for decl in decls {
1369 if decl.name == name {
1370 return Some(decl.type_id.clone());
1371 }
1372 }
1373 }
1374 }
1375 }
1376
1377 match cache.scope_parent.get(¤t_scope) {
1379 Some(&parent_id) => current_scope = parent_id,
1380 None => break, }
1382 }
1383
1384 resolve_name_to_type_id(cache, name)
1387}
1388
1389fn resolve_member_type(
1394 cache: &CompletionCache,
1395 context_type_id: &str,
1396 member_name: &str,
1397 kind: &AccessKind,
1398) -> Option<String> {
1399 let resolved_node_id = extract_node_id_from_type(context_type_id)
1400 .or_else(|| cache.type_to_node.get(context_type_id).copied())
1401 .or_else(|| {
1402 context_type_id
1404 .strip_prefix("__node_id_")
1405 .and_then(|s| s.parse::<u64>().ok())
1406 });
1407
1408 let node_id = resolved_node_id?;
1409
1410 match kind {
1411 AccessKind::Call => {
1412 cache
1414 .function_return_types
1415 .get(&(node_id, member_name.to_string()))
1416 .cloned()
1417 }
1418 AccessKind::Index => {
1419 if let Some(members) = cache.node_members.get(&node_id) {
1421 for member in members {
1422 if member.label == member_name {
1423 if let Some(tid) = cache.name_to_type.get(member_name) {
1425 if tid.starts_with("t_mapping") {
1426 return extract_mapping_value_type(tid);
1427 }
1428 return Some(tid.clone());
1429 }
1430 }
1431 }
1432 }
1433 if let Some(tid) = cache.name_to_type.get(member_name)
1435 && tid.starts_with("t_mapping")
1436 {
1437 return extract_mapping_value_type(tid);
1438 }
1439 None
1440 }
1441 AccessKind::Plain => {
1442 cache.name_to_type.get(member_name).cloned()
1444 }
1445 }
1446}
1447
1448pub struct ScopeContext {
1452 pub byte_pos: usize,
1454 pub file_id: u64,
1456}
1457
1458fn resolve_name(
1462 cache: &CompletionCache,
1463 name: &str,
1464 scope_ctx: Option<&ScopeContext>,
1465) -> Option<String> {
1466 if let Some(ctx) = scope_ctx {
1467 resolve_name_in_scope(cache, name, ctx.byte_pos, ctx.file_id)
1468 } else {
1469 resolve_name_to_type_id(cache, name)
1470 }
1471}
1472
1473pub fn get_dot_completions(
1475 cache: &CompletionCache,
1476 identifier: &str,
1477 scope_ctx: Option<&ScopeContext>,
1478) -> Vec<CompletionItem> {
1479 if let Some(items) = magic_members(identifier) {
1481 return items;
1482 }
1483
1484 let type_id = resolve_name(cache, identifier, scope_ctx);
1486
1487 if let Some(tid) = type_id {
1488 return completions_for_type(cache, &tid);
1489 }
1490
1491 vec![]
1492}
1493
1494pub fn get_chain_completions(
1497 cache: &CompletionCache,
1498 chain: &[DotSegment],
1499 scope_ctx: Option<&ScopeContext>,
1500) -> Vec<CompletionItem> {
1501 if chain.is_empty() {
1502 return vec![];
1503 }
1504
1505 if chain.len() == 1 {
1507 let seg = &chain[0];
1508
1509 match seg.kind {
1511 AccessKind::Plain => {
1512 return get_dot_completions(cache, &seg.name, scope_ctx);
1513 }
1514 AccessKind::Call => {
1515 if seg.name == "type" {
1517 return type_meta_members(seg.call_args.as_deref(), Some(cache));
1518 }
1519 if let Some(type_id) = resolve_name(cache, &seg.name, scope_ctx) {
1522 return completions_for_type(cache, &type_id);
1523 }
1524 for ((_, fn_name), ret_type) in &cache.function_return_types {
1526 if fn_name == &seg.name {
1527 return completions_for_type(cache, ret_type);
1528 }
1529 }
1530 return vec![];
1531 }
1532 AccessKind::Index => {
1533 if let Some(tid) = resolve_name(cache, &seg.name, scope_ctx)
1535 && tid.starts_with("t_mapping")
1536 && let Some(val_type) = extract_mapping_value_type(&tid)
1537 {
1538 return completions_for_type(cache, &val_type);
1539 }
1540 return vec![];
1541 }
1542 }
1543 }
1544
1545 let first = &chain[0];
1548 let mut current_type = match first.kind {
1549 AccessKind::Plain => resolve_name(cache, &first.name, scope_ctx),
1550 AccessKind::Call => {
1551 resolve_name(cache, &first.name, scope_ctx).or_else(|| {
1553 cache
1554 .function_return_types
1555 .iter()
1556 .find(|((_, fn_name), _)| fn_name == &first.name)
1557 .map(|(_, ret_type)| ret_type.clone())
1558 })
1559 }
1560 AccessKind::Index => {
1561 resolve_name(cache, &first.name, scope_ctx).and_then(|tid| {
1563 if tid.starts_with("t_mapping") {
1564 extract_mapping_value_type(&tid)
1565 } else {
1566 Some(tid)
1567 }
1568 })
1569 }
1570 };
1571
1572 for seg in &chain[1..] {
1574 let ctx_type = match ¤t_type {
1575 Some(t) => t.clone(),
1576 None => return vec![],
1577 };
1578
1579 current_type = resolve_member_type(cache, &ctx_type, &seg.name, &seg.kind);
1580 }
1581
1582 match current_type {
1584 Some(tid) => completions_for_type(cache, &tid),
1585 None => vec![],
1586 }
1587}
1588
1589pub fn get_static_completions() -> Vec<CompletionItem> {
1592 let mut items = Vec::new();
1593
1594 for kw in SOLIDITY_KEYWORDS {
1596 items.push(CompletionItem {
1597 label: kw.to_string(),
1598 kind: Some(CompletionItemKind::KEYWORD),
1599 ..Default::default()
1600 });
1601 }
1602
1603 for (name, detail) in MAGIC_GLOBALS {
1605 items.push(CompletionItem {
1606 label: name.to_string(),
1607 kind: Some(CompletionItemKind::VARIABLE),
1608 detail: Some(detail.to_string()),
1609 ..Default::default()
1610 });
1611 }
1612
1613 for (name, detail) in GLOBAL_FUNCTIONS {
1615 items.push(CompletionItem {
1616 label: name.to_string(),
1617 kind: Some(CompletionItemKind::FUNCTION),
1618 detail: Some(detail.to_string()),
1619 ..Default::default()
1620 });
1621 }
1622
1623 for (name, detail) in ETHER_UNITS {
1625 items.push(CompletionItem {
1626 label: name.to_string(),
1627 kind: Some(CompletionItemKind::UNIT),
1628 detail: Some(detail.to_string()),
1629 ..Default::default()
1630 });
1631 }
1632
1633 for (name, detail) in TIME_UNITS {
1635 items.push(CompletionItem {
1636 label: name.to_string(),
1637 kind: Some(CompletionItemKind::UNIT),
1638 detail: Some(detail.to_string()),
1639 ..Default::default()
1640 });
1641 }
1642
1643 items
1644}
1645
1646pub fn get_general_completions(cache: &CompletionCache) -> Vec<CompletionItem> {
1648 let mut items = cache.names.clone();
1649 items.extend(get_static_completions());
1650 items
1651}
1652
1653pub fn handle_completion(
1663 cache: Option<&CompletionCache>,
1664 source_text: &str,
1665 position: Position,
1666 trigger_char: Option<&str>,
1667 file_id: Option<u64>,
1668) -> Option<CompletionResponse> {
1669 let lines: Vec<&str> = source_text.lines().collect();
1670 let line = lines.get(position.line as usize)?;
1671
1672 let abs_byte = crate::utils::position_to_byte_offset(source_text, position);
1674 let line_start_byte: usize = source_text[..abs_byte]
1675 .rfind('\n')
1676 .map(|i| i + 1)
1677 .unwrap_or(0);
1678 let col_byte = (abs_byte - line_start_byte) as u32;
1679
1680 let scope_ctx = file_id.map(|fid| ScopeContext {
1682 byte_pos: abs_byte,
1683 file_id: fid,
1684 });
1685
1686 let items = if trigger_char == Some(".") {
1687 let chain = parse_dot_chain(line, col_byte);
1688 if chain.is_empty() {
1689 return None;
1690 }
1691 match cache {
1692 Some(c) => get_chain_completions(c, &chain, scope_ctx.as_ref()),
1693 None => {
1694 if chain.len() == 1 {
1696 let seg = &chain[0];
1697 if seg.name == "type" && seg.kind == AccessKind::Call {
1698 type_meta_members(seg.call_args.as_deref(), None)
1700 } else if seg.kind == AccessKind::Plain {
1701 magic_members(&seg.name).unwrap_or_default()
1702 } else {
1703 vec![]
1704 }
1705 } else {
1706 vec![]
1707 }
1708 }
1709 }
1710 } else {
1711 match cache {
1712 Some(c) => c.general_completions.clone(),
1713 None => get_static_completions(),
1714 }
1715 };
1716
1717 Some(CompletionResponse::List(CompletionList {
1718 is_incomplete: cache.is_none(),
1719 items,
1720 }))
1721}
1722
1723const SOLIDITY_KEYWORDS: &[&str] = &[
1724 "abstract",
1725 "address",
1726 "assembly",
1727 "bool",
1728 "break",
1729 "bytes",
1730 "bytes1",
1731 "bytes4",
1732 "bytes32",
1733 "calldata",
1734 "constant",
1735 "constructor",
1736 "continue",
1737 "contract",
1738 "delete",
1739 "do",
1740 "else",
1741 "emit",
1742 "enum",
1743 "error",
1744 "event",
1745 "external",
1746 "fallback",
1747 "false",
1748 "for",
1749 "function",
1750 "if",
1751 "immutable",
1752 "import",
1753 "indexed",
1754 "int8",
1755 "int24",
1756 "int128",
1757 "int256",
1758 "interface",
1759 "internal",
1760 "library",
1761 "mapping",
1762 "memory",
1763 "modifier",
1764 "new",
1765 "override",
1766 "payable",
1767 "pragma",
1768 "private",
1769 "public",
1770 "pure",
1771 "receive",
1772 "return",
1773 "returns",
1774 "revert",
1775 "storage",
1776 "string",
1777 "struct",
1778 "true",
1779 "type",
1780 "uint8",
1781 "uint24",
1782 "uint128",
1783 "uint160",
1784 "uint256",
1785 "unchecked",
1786 "using",
1787 "view",
1788 "virtual",
1789 "while",
1790];
1791
1792const ETHER_UNITS: &[(&str, &str)] = &[("wei", "1"), ("gwei", "1e9"), ("ether", "1e18")];
1794
1795const TIME_UNITS: &[(&str, &str)] = &[
1797 ("seconds", "1"),
1798 ("minutes", "60 seconds"),
1799 ("hours", "3600 seconds"),
1800 ("days", "86400 seconds"),
1801 ("weeks", "604800 seconds"),
1802];
1803
1804const MAGIC_GLOBALS: &[(&str, &str)] = &[
1805 ("msg", "msg"),
1806 ("block", "block"),
1807 ("tx", "tx"),
1808 ("abi", "abi"),
1809 ("this", "address"),
1810 ("super", "contract"),
1811 ("type", "type information"),
1812];
1813
1814const GLOBAL_FUNCTIONS: &[(&str, &str)] = &[
1815 ("addmod(uint256, uint256, uint256)", "uint256"),
1817 ("mulmod(uint256, uint256, uint256)", "uint256"),
1818 ("keccak256(bytes memory)", "bytes32"),
1819 ("sha256(bytes memory)", "bytes32"),
1820 ("ripemd160(bytes memory)", "bytes20"),
1821 (
1822 "ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)",
1823 "address",
1824 ),
1825 ("blockhash(uint256 blockNumber)", "bytes32"),
1827 ("blobhash(uint256 index)", "bytes32"),
1828 ("gasleft()", "uint256"),
1829 ("assert(bool condition)", ""),
1831 ("require(bool condition)", ""),
1832 ("require(bool condition, string memory message)", ""),
1833 ("revert()", ""),
1834 ("revert(string memory reason)", ""),
1835 ("selfdestruct(address payable recipient)", ""),
1837];