fn build_modifier_definition_map(
contract: &ContractIR,
) -> std::collections::HashMap<(String, usize), FunctionIR> {
let mut map: std::collections::HashMap<(String, usize), FunctionIR> =
std::collections::HashMap::new();
for func in &contract.functions {
if matches!(func.ty, FunctionTy::Modifier) {
let key = (func.name.clone(), func.parameters.len());
match map.get(&key) {
Some(existing) => {
if existing.body.is_none() && func.body.is_some() {
map.insert(key, func.clone());
}
}
None => {
map.insert(key, func.clone());
}
}
}
}
map
}
fn apply_modifier_calls_to_body(
original_body: &Statement,
modifier_calls: &[Base],
modifier_defs: &std::collections::HashMap<(String, usize), FunctionIR>,
) -> Result<Statement, SolidityError> {
let (body, _had_epilogue) =
apply_modifier_calls_to_body_with_epilogue(original_body, modifier_calls, modifier_defs)?;
Ok(body)
}
fn apply_modifier_calls_to_body_with_epilogue(
original_body: &Statement,
modifier_calls: &[Base],
modifier_defs: &std::collections::HashMap<(String, usize), FunctionIR>,
) -> Result<(Statement, bool), SolidityError> {
let mut current = original_body.clone();
let mut has_epilogue = false;
for modifier_call in modifier_calls.iter() {
let Some(name) = base_last_name(modifier_call) else {
continue;
};
let arg_count = modifier_call.args.as_ref().map(|a| a.len()).unwrap_or(0);
let Some(modifier_def) = modifier_defs.get(&(name, arg_count)) else {
continue;
};
let Some(modifier_body) = modifier_def.body.as_ref() else {
continue;
};
if modifier_body_has_epilogue(modifier_body) {
has_epilogue = true;
break;
}
}
if has_epilogue {
current = Statement::DoWhile(
Loc::Implicit,
Box::new(current),
Expression::BoolLiteral(Loc::Implicit, false),
);
}
for modifier_call in modifier_calls.iter().rev() {
let Some(name) = base_last_name(modifier_call) else {
continue;
};
let arg_list: Vec<Expression> = modifier_call.args.clone().unwrap_or_default();
let key = (name.clone(), arg_list.len());
let Some(modifier_def) = modifier_defs.get(&key) else {
return Err(SolidityError::Analysis(format!(
"unresolved modifier '{name}' with {} argument(s)",
arg_list.len()
)));
};
let Some(modifier_body) = modifier_def.body.as_ref() else {
continue;
};
let mut normalized_args = arg_list.clone();
if normalized_args.len() < modifier_def.parameters.len() {
let missing = modifier_def.parameters.len() - normalized_args.len();
for _ in 0..missing {
normalized_args.push(Expression::NumberLiteral(
Default::default(),
"0".to_string(),
"".to_string(),
None,
));
}
} else if normalized_args.len() > modifier_def.parameters.len() {
normalized_args.truncate(modifier_def.parameters.len());
}
let substitutions = build_parameter_substitutions(&modifier_def.parameters, &normalized_args)?;
current = rewrite_statement(modifier_body, &substitutions, Some(¤t));
}
Ok((current, has_epilogue))
}
fn modifier_body_has_epilogue(body: &Statement) -> bool {
match body {
Statement::Block { statements, .. } => {
let mut seen_placeholder = false;
for stmt in statements {
if seen_placeholder {
return true;
}
if modifier_placeholder_stmt(stmt) {
seen_placeholder = true;
continue;
}
if modifier_body_has_epilogue(stmt) {
return true;
}
}
false
}
Statement::If(_, _, then_stmt, else_stmt) => {
modifier_body_has_epilogue(then_stmt)
|| else_stmt
.as_ref()
.map(|s| modifier_body_has_epilogue(s))
.unwrap_or(false)
}
Statement::While(_, _, body) | Statement::DoWhile(_, body, _) => {
modifier_body_has_epilogue(body)
}
Statement::For(_, _, _, _, body) => body
.as_ref()
.map(|b| modifier_body_has_epilogue(b))
.unwrap_or(false),
_ => false,
}
}
fn statement_list_from_body(body: &Statement) -> Vec<Statement> {
match body {
Statement::Block { statements, .. } => statements.clone(),
stmt => vec![stmt.clone()],
}
}