neo_solidity/ir/expressions/calls/builtins/
helpers.rs1fn manifest_type_name(ty: ManifestType) -> &'static str {
2 match ty {
3 ManifestType::Integer => "Integer",
4 ManifestType::Boolean => "Boolean",
5 ManifestType::String => "String",
6 ManifestType::Hash160 => "Hash160",
7 ManifestType::Hash256 => "Hash256",
8 ManifestType::ByteArray => "ByteArray",
9 ManifestType::Array => "Array",
10 ManifestType::Map => "Map",
11 ManifestType::Any => "Any",
12 }
13}
14
15fn value_type_satisfies_manifest_type(actual: &ValueType, expected: ManifestType) -> Option<bool> {
16 if expected == ManifestType::Any {
17 return Some(true);
18 }
19
20 match actual {
21 ValueType::Any => None,
22 ValueType::Integer { .. } => Some(expected == ManifestType::Integer),
23 ValueType::Boolean => Some(expected == ManifestType::Boolean),
24 ValueType::String => Some(matches!(expected, ManifestType::String | ManifestType::ByteArray)),
25 ValueType::Address => Some(matches!(expected, ManifestType::Hash160 | ManifestType::ByteArray)),
26 ValueType::ByteArray { fixed_len } => match expected {
27 ManifestType::Hash160 => Some(matches!(fixed_len, Some(20))),
28 ManifestType::Hash256 => Some(matches!(fixed_len, Some(32))),
29 ManifestType::ByteArray => Some(true),
30 _ => Some(false),
31 },
32 ValueType::Array(_) => Some(expected == ManifestType::Array),
33 ValueType::Mapping { .. } => Some(expected == ManifestType::Map),
34 ValueType::Struct { .. } => Some(expected == ManifestType::Array),
35 }
36}
37
38fn extract_string_literal(expr: &Expression) -> Option<String> {
39 match expr {
40 Expression::StringLiteral(parts) => {
41 Some(String::from_utf8_lossy(&string_literal_bytes(parts)).to_string())
42 }
43 _ => None,
44 }
45}
46
47fn extract_abi_encode_args(expr: &Expression) -> Option<&[Expression]> {
48 let Expression::FunctionCall(_, func, args) = expr else {
49 return None;
50 };
51
52 let Expression::MemberAccess(_, inner, member) = func.as_ref() else {
53 return None;
54 };
55 let Expression::Variable(base) = inner.as_ref() else {
56 return None;
57 };
58
59 if base.name == "abi" && (member.name == "encode" || member.name == "encodePacked" || member.name == "encodeCall") {
60 Some(args.as_slice())
61 } else {
62 None
63 }
64}
65
66fn validate_runtime_notify_call(args: &[Expression], ctx: &mut LoweringContext) {
67 if args.len() != 2 {
68 return;
69 }
70
71 let Some(event_name) = extract_string_literal(&args[0]) else {
72 return;
73 };
74
75 let Some(expected_sig) = ctx.event_signature(&event_name).map(|sig| sig.to_vec()) else {
76 ctx.record_error(format!(
77 "Runtime.notify refers to event '{event_name}' which is not declared; declare a matching Solidity `event` so it is included in the contract manifest ABI"
78 ));
79 return;
80 };
81
82 let Some(encoded_args) = extract_abi_encode_args(&args[1]) else {
83 return;
84 };
85
86 if expected_sig.len() != encoded_args.len() {
87 ctx.record_error(format!(
88 "Runtime.notify event '{event_name}' expects {} argument(s), but abi.encode(...) provides {}",
89 expected_sig.len(),
90 encoded_args.len()
91 ));
92 return;
93 }
94
95 for (index, (expected_ty, arg_expr)) in expected_sig
96 .iter()
97 .copied()
98 .zip(encoded_args.iter())
99 .enumerate()
100 {
101 if expected_ty == ManifestType::Any {
102 continue;
103 }
104
105 let Some(actual_ty) = infer_type_from_expression(arg_expr, ctx) else {
106 continue;
107 };
108
109 if value_type_satisfies_manifest_type(&actual_ty, expected_ty) == Some(false) {
110 ctx.record_error(format!(
111 "Runtime.notify event '{event_name}' argument #{} has incompatible type (expected {}, got {:?})",
112 index,
113 manifest_type_name(expected_ty),
114 actual_ty
115 ));
116 }
117 }
118}