Skip to content

Commit 0816faf

Browse files
committed
feat(slang): encode require string literal message
Extract the string literal from the second argument of require(cond, "msg") and pass it into sol.require's msg attribute. Previously the message was always hardcoded to an empty string.
1 parent 54a46a7 commit 0816faf

11 files changed

Lines changed: 123 additions & 45 deletions

File tree

Cargo.lock

Lines changed: 29 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solx-mlir/sol_attr_stubs.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,11 @@ MlirType solxCreateContractType(MlirContext ctx, const char *name_ptr,
6969
return wrap(mlir::sol::ContractType::get(context, name, payable));
7070
}
7171

72+
MlirType solxCreateStringType(MlirContext ctx, uint32_t dataLocation) {
73+
if (dataLocation > 5) abort();
74+
auto *context = unwrap(ctx);
75+
auto location = static_cast<mlir::sol::DataLocation>(dataLocation);
76+
return wrap(mlir::sol::StringType::get(context, location));
77+
}
78+
7279
} /* extern "C" */

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

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ use crate::ods::sol::ReturnOperation;
5353
use crate::ods::sol::RevertOperation;
5454
use crate::ods::sol::StateVarOperation;
5555
use crate::ods::sol::StoreOperation;
56+
use crate::ods::sol::StringLitOperation;
5657
use crate::ods::sol::WhileOperation;
5758
use crate::ods::sol::YieldOperation;
5859

@@ -311,6 +312,31 @@ impl<'context> Builder<'context> {
311312
.into()
312313
}
313314

315+
// ==== String literals ====
316+
317+
/// Emits a `sol.string_lit` constant with a `!sol.string<Memory>` result.
318+
///
319+
/// # Panics
320+
///
321+
/// Panics if the MLIR operation cannot be constructed, indicating a bug in the builder.
322+
pub fn emit_sol_string_lit<'block, B>(&self, value: &str, block: &B) -> Value<'context, 'block>
323+
where
324+
B: BlockLike<'context, 'block>,
325+
'context: 'block,
326+
{
327+
block
328+
.append_operation(
329+
StringLitOperation::builder(self.context, self.unknown_location)
330+
.value(StringAttribute::new(self.context, value))
331+
.addr(self.types.sol_string_memory)
332+
.build()
333+
.into(),
334+
)
335+
.result(0)
336+
.expect("sol.string_lit always produces one result")
337+
.into()
338+
}
339+
314340
// ==== Terminators ====
315341

316342
/// Emits a `sol.revert` with an empty signature (no error data).
@@ -352,27 +378,31 @@ impl<'context> Builder<'context> {
352378
);
353379
}
354380

355-
/// Emits a `sol.require` conditional revert with an empty signature.
381+
/// Emits a `sol.require` conditional revert with an optional message.
356382
///
357-
/// Reverts if `condition` is false. Not a terminator — execution continues
358-
/// after this op when the condition is true.
383+
/// Reverts if `condition` is false. When `msg` is `Some`, the revert
384+
/// includes the string as a revert reason. Not a terminator — execution
385+
/// continues after this op when the condition is true.
359386
///
360387
/// # Panics
361388
///
362389
/// Panics if the MLIR operation cannot be constructed, indicating a bug in the builder.
363-
pub fn emit_sol_require<'block, B>(&self, condition: Value<'context, 'block>, block: &B)
364-
where
390+
pub fn emit_sol_require<'block, B>(
391+
&self,
392+
condition: Value<'context, 'block>,
393+
msg: Option<&str>,
394+
block: &B,
395+
) where
365396
B: BlockLike<'context, 'block>,
366397
'context: 'block,
367398
{
368-
block.append_operation(
369-
RequireOperation::builder(self.context, self.unknown_location)
370-
.cond(condition)
371-
.msg(StringAttribute::new(self.context, ""))
372-
.args(&[])
373-
.build()
374-
.into(),
375-
);
399+
let mut builder = RequireOperation::builder(self.context, self.unknown_location)
400+
.cond(condition)
401+
.args(&[]);
402+
if let Some(msg) = msg {
403+
builder = builder.msg(StringAttribute::new(self.context, msg));
404+
}
405+
block.append_operation(builder.build().into());
376406
}
377407

378408
/// Emits a `sol.return` terminator.

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub struct TypeFactory<'context> {
2626
pub sol_address: Type<'context>,
2727
/// Sol storage pointer type (`!sol.ptr<ui256, Storage>`).
2828
pub sol_ptr_storage: Type<'context>,
29+
/// Sol memory string type (`!sol.string<Memory>`).
30+
pub sol_string_memory: Type<'context>,
2931
}
3032

3133
impl<'context> TypeFactory<'context> {
@@ -59,13 +61,22 @@ impl<'context> TypeFactory<'context> {
5961
solx_utils::DataLocation::Storage as u32,
6062
))
6163
};
64+
// SAFETY: `solxCreateStringType` returns a valid MlirType from
65+
// the C++ Sol dialect. The context pointer is valid.
66+
let sol_string_memory = unsafe {
67+
Type::from_raw(crate::ffi::solxCreateStringType(
68+
context.to_raw(),
69+
solx_utils::DataLocation::Memory as u32,
70+
))
71+
};
6272
Self {
6373
context,
6474
i1,
6575
ui160,
6676
ui256,
6777
sol_address,
6878
sol_ptr_storage,
79+
sol_string_memory,
6980
}
7081
}
7182

solx-mlir/src/ffi.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ unsafe extern "C" {
104104
payable: bool,
105105
) -> mlir_sys::MlirType;
106106

107+
/// Creates a `sol::StringType` with the given data location.
108+
///
109+
/// `data_location` maps to `mlir::sol::DataLocation` (0=Storage, 1=CallData,
110+
/// 2=Memory, 3=Stack, 4=Immutable, 5=Transient).
111+
pub fn solxCreateStringType(context: MlirContext, data_location: u32) -> mlir_sys::MlirType;
112+
107113
// ---- MLIR core (not in mlir-sys) ----
108114

109115
/// Returns the region that owns the given block.

solx-slang/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ semver.workspace = true
1616
# `__private_testing_utils` is required by `__private_backend_api` internals
1717
# (binder/types access in node_extensions). Both are unstable Slang APIs.
1818
# TODO: pin to a release tag instead of branch = "main"
19-
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "a01bc76b742173941cea1aff0b3b68a9006b38b8", features = ["__private_backend_api", "__private_testing_utils"] }
19+
slang_solidity = { git = "https://github.com/NomicFoundation/slang.git", rev = "6f892dff8e2d6d275cfe9737c3cb364486c41410", features = ["__private_backend_api", "__private_testing_utils"] }
2020
melior = { git = "https://github.com/NomicFoundation/melior", rev = "62a909e2ff67fcc19f7f42dc4fb70c3e03376bd2", features = ["ods-dialects", "helpers"] }
2121

2222
solx-codegen-evm = { path = "../solx-codegen-evm" }

solx-slang/src/ast/contract/function/expression/arithmetic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
132132
.state
133133
.builder
134134
.emit_sol_cmp(value, zero, CmpPredicate::Eq, &block);
135-
let result_type = target_type.unwrap_or_else(|| self.state.builder.types.ui256);
135+
let result_type = target_type.unwrap_or(self.state.builder.types.ui256);
136136
let result = TypeConversion::from_target_type(result_type, &self.state.builder)
137137
.emit(cmp, &self.state.builder, &block);
138138
Ok((result, block))

solx-slang/src/ast/contract/function/expression/call/mod.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,18 @@ impl<'emitter, 'state, 'context, 'block> CallEmitter<'emitter, 'state, 'context,
102102
&& (positional_arguments.len() == 1
103103
|| positional_arguments.len() == Self::MAX_REQUIRE_ARGUMENTS)
104104
{
105-
// TODO: encode revert reason from second argument
106-
let first = positional_arguments
107-
.iter()
108-
.next()
109-
.expect("length checked above");
110-
let block = self.emit_require(&first, block)?;
105+
let mut args = positional_arguments.iter();
106+
let condition = args.next().expect("length checked above");
107+
let message_arg = args.next();
108+
let message = match &message_arg {
109+
Some(Expression::StringExpression(string_expression)) => {
110+
let bytes = string_expression.value();
111+
Some(String::from_utf8(bytes).expect("require message is valid UTF-8"))
112+
}
113+
Some(_) => anyhow::bail!("require message must be a string literal"),
114+
None => None,
115+
};
116+
let block = self.emit_require(&condition, message.as_deref(), block)?;
111117
return Ok((None, block));
112118
}
113119

@@ -242,10 +248,12 @@ impl<'emitter, 'state, 'context, 'block> CallEmitter<'emitter, 'state, 'context,
242248
Ok(block)
243249
}
244250

245-
/// Emits a `require(condition)` built-in via `sol.require`.
251+
/// Emits a `require(condition)` or `require(condition, "message")` built-in
252+
/// via `sol.require`.
246253
fn emit_require(
247254
&self,
248255
condition: &Expression,
256+
message: Option<&str>,
249257
block: BlockRef<'context, 'block>,
250258
) -> anyhow::Result<BlockRef<'context, 'block>> {
251259
let (condition_value, block) = self.expression_emitter.emit_value(condition, block)?;
@@ -256,7 +264,7 @@ impl<'emitter, 'state, 'context, 'block> CallEmitter<'emitter, 'state, 'context,
256264
self.expression_emitter
257265
.state
258266
.builder
259-
.emit_sol_require(condition_boolean, &block);
267+
.emit_sol_require(condition_boolean, message, &block);
260268

261269
Ok(block)
262270
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
154154
.into();
155155
Ok((Some(value), block))
156156
}
157+
Expression::StringExpression(string_expression) => {
158+
let bytes = string_expression.value();
159+
let text = std::str::from_utf8(&bytes).expect("string literal is valid UTF-8");
160+
let value = self.state.builder.emit_sol_string_lit(text, &block);
161+
Ok((Some(value), block))
162+
}
157163
Expression::Identifier(identifier) => {
158164
let name = identifier.name();
159165
match identifier.resolve_to_definition() {
@@ -302,7 +308,7 @@ impl<'state, 'context, 'block> ExpressionEmitter<'state, 'context, 'block> {
302308
Expression::ConditionalExpression(conditional) => {
303309
let result_type = self
304310
.resolve_expression_type(conditional.node_id())
305-
.unwrap_or_else(|| self.state.builder.types.ui256);
311+
.unwrap_or(self.state.builder.types.ui256);
306312
let condition = conditional.operand();
307313
let (condition_value, block) = self.emit_value(&condition, block)?;
308314
let condition_boolean = self.emit_is_nonzero(condition_value, &block);

0 commit comments

Comments
 (0)