fn string_literal_bytes(parts: &[PtStringLiteral]) -> Vec<u8> {
let mut bytes = Vec::new();
for part in parts {
bytes.extend_from_slice(&unescape_solidity_string(&part.string));
}
bytes
}
fn unescape_solidity_string(raw: &str) -> Vec<u8> {
let bytes = raw.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if c != b'\\' || i + 1 >= bytes.len() {
out.push(c);
i += 1;
continue;
}
let next = bytes[i + 1];
match next {
b'n' => {
out.push(0x0A);
i += 2;
}
b't' => {
out.push(0x09);
i += 2;
}
b'r' => {
out.push(0x0D);
i += 2;
}
b'"' => {
out.push(0x22);
i += 2;
}
b'\'' => {
out.push(0x27);
i += 2;
}
b'\\' => {
out.push(0x5C);
i += 2;
}
b'0' => {
out.push(0x00);
i += 2;
}
b'x' if i + 3 < bytes.len() => {
let hi = (bytes[i + 2] as char).to_digit(16);
let lo = (bytes[i + 3] as char).to_digit(16);
if let (Some(h), Some(l)) = (hi, lo) {
out.push(((h << 4) | l) as u8);
i += 4;
} else {
out.push(c);
i += 1;
}
}
b'u' if i + 5 < bytes.len() => {
let d0 = (bytes[i + 2] as char).to_digit(16);
let d1 = (bytes[i + 3] as char).to_digit(16);
let d2 = (bytes[i + 4] as char).to_digit(16);
let d3 = (bytes[i + 5] as char).to_digit(16);
if let (Some(a), Some(b), Some(c_), Some(d)) = (d0, d1, d2, d3) {
let cp = (a << 12) | (b << 8) | (c_ << 4) | d;
if let Some(ch) = char::from_u32(cp) {
let mut buf = [0u8; 4];
out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
i += 6;
} else {
out.push(c);
i += 1;
}
} else {
out.push(c);
i += 1;
}
}
_ => {
out.push(c);
i += 1;
}
}
}
out
}
fn extract_signature_string(expr: &Expression) -> Option<String> {
match expr {
Expression::StringLiteral(parts) => {
Some(String::from_utf8_lossy(&string_literal_bytes(parts)).to_string())
}
Expression::FunctionCall(_, func, args) => {
if args.len() == 1 {
match func.as_ref() {
Expression::Type(_, _) => extract_signature_string(&args[0]),
Expression::Variable(id) if id.name == "bytes" || id.name == "string" => {
extract_signature_string(&args[0])
}
_ => None,
}
} else {
None
}
}
_ => None,
}
}
fn resolve_selector_method_name(expr: &Expression, ctx: &LoweringContext) -> Option<String> {
match expr {
Expression::Variable(identifier) => {
if let Some(state_index) = ctx.state_index_map.get(&identifier.name).copied() {
if let Some(meta) = ctx.state_metadata(state_index) {
if meta.is_constant {
if let Some(initializer) = meta.initializer.as_ref() {
return resolve_selector_method_name(initializer, ctx);
}
}
}
}
None
}
Expression::MemberAccess(_, inner, member) => {
if member.name == "selector" {
match inner.as_ref() {
Expression::MemberAccess(_, _, function_name)
if !function_name.name.trim().is_empty() =>
{
return Some(function_name.name.clone());
}
Expression::Variable(function_name)
if !function_name.name.trim().is_empty() =>
{
return Some(function_name.name.clone());
}
_ => {}
}
}
None
}
Expression::FunctionCall(_, func, args) => {
if matches!(func.as_ref(), Expression::Type(_, _)) && args.len() == 1 {
return resolve_selector_method_name(&args[0], ctx);
}
if let Expression::Variable(id) = func.as_ref() {
if id.name == "keccak256" && args.len() == 1 {
if let Some(signature) = extract_signature_string(&args[0]) {
let name = signature
.split('(')
.next()
.unwrap_or(signature.as_str())
.trim()
.to_string();
if !name.is_empty() {
return Some(name);
}
}
}
}
None
}
_ => None,
}
}