neo_solidity/ir/expressions/calls/builtins/
helpers.rs

1fn 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}