Skip to content

Commit 6137409

Browse files
committed
feat(slang): encode custom error data in revert statements
Encode the error signature and arguments into sol.revert for revert CustomError(arg1, arg2) statements. Uses slang's compute_canonical_signature() from NomicFoundation/slang#1712. Blocked on merging the slang StringExpression::value() branch with the compute_canonical_signature branch.
1 parent d65c000 commit 6137409

3 files changed

Lines changed: 183 additions & 20 deletions

File tree

solx-mlir/src/context/builder/mod.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -339,25 +339,35 @@ impl<'context> Builder<'context> {
339339

340340
// ==== Terminators ====
341341

342-
/// Emits a `sol.revert` with an empty signature (no error data).
342+
/// Emits a `sol.revert` with an optional error signature and arguments.
343+
///
344+
/// For custom errors (`revert MyError(x, y)`), pass the canonical
345+
/// signature (e.g. `"MyError(uint256,address)"`) and the evaluated
346+
/// argument values. For plain reverts, pass an empty signature and no
347+
/// args.
343348
// TODO(sol-dialect): mark `sol.revert` as `IsTerminator` like `sol.return`
344349
// so callers don't need to append `llvm.unreachable` after it.
345350
///
346351
/// # Panics
347352
///
348353
/// Panics if the MLIR operation cannot be constructed, indicating a bug in the builder.
349-
pub fn emit_sol_revert<'block, B>(&self, block: &B)
350-
where
354+
pub fn emit_sol_revert<'block, B>(
355+
&self,
356+
signature: &str,
357+
args: &[Value<'context, 'block>],
358+
is_custom_error: bool,
359+
block: &B,
360+
) where
351361
B: BlockLike<'context, 'block>,
352362
'context: 'block,
353363
{
354-
block.append_operation(
355-
RevertOperation::builder(self.context, self.unknown_location)
356-
.signature(StringAttribute::new(self.context, ""))
357-
.args(&[])
358-
.build()
359-
.into(),
360-
);
364+
let mut builder = RevertOperation::builder(self.context, self.unknown_location)
365+
.signature(StringAttribute::new(self.context, signature))
366+
.args(args);
367+
if is_custom_error {
368+
builder = builder.call(Attribute::unit(self.context));
369+
}
370+
block.append_operation(builder.build().into());
361371
}
362372

363373
/// Emits a `sol.assert` panic if the condition is false.

solx-slang/src/ast/contract/function/statement/mod.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
//!
44
55
pub mod control_flow;
6+
mod revert;
67

78
use std::collections::HashMap;
89
use std::rc::Rc;
910

10-
use melior::ir::BlockLike;
1111
use melior::ir::BlockRef;
1212
use melior::ir::Region;
1313
use melior::ir::Type;
@@ -130,15 +130,7 @@ impl<'state, 'context, 'block> StatementEmitter<'state, 'context, 'block> {
130130
self.checked = saved_checked;
131131
result
132132
}
133-
Statement::RevertStatement(_revert) => {
134-
// TODO: encode custom error data from revert arguments
135-
self.state.builder.emit_sol_revert(&block);
136-
// TODO(sol-dialect): remove once sol.revert is marked IsTerminator
137-
block.append_operation(melior::dialect::llvm::unreachable(
138-
self.state.builder.unknown_location,
139-
));
140-
Ok(None)
141-
}
133+
Statement::RevertStatement(revert) => self.emit_revert(revert, block),
142134
_ => anyhow::bail!(
143135
"unsupported statement: {:?}",
144136
std::mem::discriminant(statement)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//!
2+
//! Revert statement lowering.
3+
//!
4+
5+
use std::collections::HashMap;
6+
7+
use melior::ir::BlockLike;
8+
use melior::ir::BlockRef;
9+
use melior::ir::Value;
10+
use slang_solidity::backend::ir::ast::ArgumentsDeclaration;
11+
use slang_solidity::backend::ir::ast::Definition;
12+
use slang_solidity::backend::ir::ast::Expression;
13+
use slang_solidity::backend::ir::ast::NamedArguments;
14+
use slang_solidity::backend::ir::ast::Parameters;
15+
use slang_solidity::backend::ir::ast::RevertStatement;
16+
17+
use super::StatementEmitter;
18+
use crate::ast::contract::function::expression::ExpressionEmitter;
19+
20+
impl<'state, 'context, 'block> StatementEmitter<'state, 'context, 'block> {
21+
/// Emits a `sol.revert` with optional custom error signature and arguments.
22+
pub(super) fn emit_revert(
23+
&self,
24+
revert: &RevertStatement,
25+
block: BlockRef<'context, 'block>,
26+
) -> anyhow::Result<Option<BlockRef<'context, 'block>>> {
27+
let Some((signature, argument_values, current_block)) =
28+
self.emit_custom_error_revert_data(revert, block)?
29+
else {
30+
self.emit_revert_terminator("", &[], false, block);
31+
return Ok(None);
32+
};
33+
34+
self.emit_revert_terminator(&signature, &argument_values, true, current_block);
35+
Ok(None)
36+
}
37+
38+
/// Resolves and evaluates custom error data for a revert statement.
39+
fn emit_custom_error_revert_data(
40+
&self,
41+
revert: &RevertStatement,
42+
block: BlockRef<'context, 'block>,
43+
) -> anyhow::Result<
44+
Option<(
45+
String,
46+
Vec<Value<'context, 'block>>,
47+
BlockRef<'context, 'block>,
48+
)>,
49+
> {
50+
let Some(Definition::Error(error)) = revert.error().resolve_to_definition() else {
51+
return Ok(None);
52+
};
53+
54+
let signature = error
55+
.compute_canonical_signature()
56+
.ok_or_else(|| anyhow::anyhow!("cannot compute canonical signature for error"))?;
57+
let arguments = revert.arguments();
58+
let parameters = error.parameters();
59+
let (argument_values, current_block) =
60+
self.emit_revert_arguments(&arguments, &parameters, block)?;
61+
62+
Ok(Some((signature, argument_values, current_block)))
63+
}
64+
65+
/// Evaluates positional or named revert arguments in ABI order.
66+
fn emit_revert_arguments(
67+
&self,
68+
arguments: &ArgumentsDeclaration,
69+
error_parameters: &Parameters,
70+
block: BlockRef<'context, 'block>,
71+
) -> anyhow::Result<(Vec<Value<'context, 'block>>, BlockRef<'context, 'block>)> {
72+
match arguments {
73+
ArgumentsDeclaration::PositionalArguments(positional) => {
74+
self.emit_revert_argument_values(positional.iter(), block)
75+
}
76+
ArgumentsDeclaration::NamedArguments(named) => {
77+
let arguments = Self::order_named_revert_arguments(named, error_parameters)?;
78+
self.emit_revert_argument_values(arguments, block)
79+
}
80+
}
81+
}
82+
83+
/// Orders named revert arguments by the custom error's parameter order.
84+
fn order_named_revert_arguments(
85+
named_arguments: &NamedArguments,
86+
error_parameters: &Parameters,
87+
) -> anyhow::Result<Vec<Expression>> {
88+
let mut arguments = HashMap::new();
89+
for argument in named_arguments.iter() {
90+
let name = argument.name().name();
91+
let previous = arguments.insert(name.clone(), argument.value());
92+
anyhow::ensure!(
93+
previous.is_none(),
94+
"duplicate named revert argument `{name}`"
95+
);
96+
}
97+
98+
let mut ordered_arguments = Vec::new();
99+
for parameter in error_parameters.iter() {
100+
let parameter_name = parameter
101+
.name()
102+
.ok_or_else(|| {
103+
anyhow::anyhow!("cannot match named revert argument to unnamed error parameter")
104+
})?
105+
.name();
106+
let argument = arguments.remove(&parameter_name).ok_or_else(|| {
107+
anyhow::anyhow!("missing named revert argument `{parameter_name}`")
108+
})?;
109+
ordered_arguments.push(argument);
110+
}
111+
112+
anyhow::ensure!(
113+
arguments.is_empty(),
114+
"unknown named revert argument(s): {}",
115+
arguments.keys().cloned().collect::<Vec<_>>().join(", ")
116+
);
117+
118+
Ok(ordered_arguments)
119+
}
120+
121+
/// Evaluates revert argument expressions from left to right.
122+
fn emit_revert_argument_values(
123+
&self,
124+
arguments: impl IntoIterator<Item = Expression>,
125+
block: BlockRef<'context, 'block>,
126+
) -> anyhow::Result<(Vec<Value<'context, 'block>>, BlockRef<'context, 'block>)> {
127+
let emitter = ExpressionEmitter::new(
128+
&self.semantic,
129+
self.state,
130+
self.environment,
131+
self.storage_layout,
132+
self.checked,
133+
);
134+
let mut argument_values = Vec::new();
135+
let mut current_block = block;
136+
for argument in arguments {
137+
let (value, next_block) = emitter.emit_value(&argument, current_block)?;
138+
argument_values.push(value);
139+
current_block = next_block;
140+
}
141+
142+
Ok((argument_values, current_block))
143+
}
144+
145+
/// Emits a revert terminator followed by the temporary unreachable.
146+
fn emit_revert_terminator(
147+
&self,
148+
signature: &str,
149+
argument_values: &[Value<'context, 'block>],
150+
is_custom_error: bool,
151+
block: BlockRef<'context, 'block>,
152+
) {
153+
self.state
154+
.builder
155+
.emit_sol_revert(signature, argument_values, is_custom_error, &block);
156+
// TODO(sol-dialect): remove once sol.revert is marked IsTerminator
157+
block.append_operation(melior::dialect::llvm::unreachable(
158+
self.state.builder.unknown_location,
159+
));
160+
}
161+
}

0 commit comments

Comments
 (0)