forge/brutalizer/
value.rs1use solar::ast::{ElementaryType, Span, Type, TypeKind, TypeSize};
2
3use super::span_seed;
4
5pub(super) fn deterministic_mask(span: Span) -> String {
6 let h = span_seed(span);
7 let mask = if h == 0 { 1 } else { h };
8 format!("0x{mask:016x}")
9}
10
11pub(super) fn brutalize_cast(ty: &Type<'_>, arg_text: &str, mask: &str) -> Option<String> {
12 match &ty.kind {
13 TypeKind::Elementary(elem_ty) => match elem_ty {
14 ElementaryType::Address(payable) => Some(brutalize_address(*payable, arg_text, mask)),
15 ElementaryType::UInt(size) => brutalize_uint(*size, arg_text, mask),
16 ElementaryType::Int(size) => brutalize_int(*size, arg_text, mask),
17 ElementaryType::FixedBytes(size) => brutalize_fixed_bytes(*size, arg_text, mask),
18 ElementaryType::Bool => None,
19 ElementaryType::Bytes | ElementaryType::String => None,
20 ElementaryType::Fixed(..) | ElementaryType::UFixed(..) => None,
21 },
22 _ => None,
23 }
24}
25
26pub(super) fn brutalize_payable_address(arg_text: &str, mask: &str) -> String {
27 brutalize_address(true, arg_text, mask)
28}
29
30fn brutalize_address(payable: bool, arg_text: &str, mask: &str) -> String {
31 let expr = format!(
32 "address(uint160(uint256(uint160(address({arg_text}))) | (uint256({mask}) << 160)))"
33 );
34 if payable { format!("payable({expr})") } else { expr }
35}
36
37fn brutalize_uint(size: TypeSize, arg_text: &str, mask: &str) -> Option<String> {
38 let bits = size.bits_raw();
39 let actual_bits = if bits == 0 { 256 } else { bits };
40 if actual_bits >= 256 {
41 return None;
42 }
43 let mask = dirty_mask(mask, usize::from(256 - actual_bits));
44 Some(format!(
45 "uint{actual_bits}(uint256(uint{actual_bits}({arg_text})) | (uint256({mask}) << {actual_bits}))"
46 ))
47}
48
49fn brutalize_int(size: TypeSize, arg_text: &str, mask: &str) -> Option<String> {
50 let bits = size.bits_raw();
51 let actual_bits = if bits == 0 { 256 } else { bits };
52 if actual_bits >= 256 {
53 return None;
54 }
55 let mask = dirty_mask(mask, usize::from(256 - actual_bits));
56 Some(format!(
57 "int{actual_bits}(int256(int{actual_bits}({arg_text})) ^ int256(uint256({mask}) << {actual_bits}))"
58 ))
59}
60
61fn brutalize_fixed_bytes(size: TypeSize, arg_text: &str, mask: &str) -> Option<String> {
62 let bytes = size.bytes_raw();
63 if bytes >= 32 || bytes == 0 {
64 return None;
65 }
66 let unused_bits = (32 - bytes) * 8;
67 let mask = dirty_mask(mask, usize::from(unused_bits));
68 Some(format!(
69 "bytes{bytes}(bytes32(bytes{bytes}({arg_text})) | bytes32(uint256({mask}) & ((uint256(1) << {unused_bits}) - 1)))"
70 ))
71}
72
73fn dirty_mask(mask: &str, unused_bits: usize) -> String {
74 let Ok(mut value) = u64::from_str_radix(mask.trim_start_matches("0x"), 16) else {
75 return mask.to_string();
76 };
77 if unused_bits < 64 {
78 let width_mask = (1u64 << unused_bits) - 1;
79 value &= width_mask;
80 if value == 0 {
81 value = 1;
82 }
83 }
84 format!("0x{value:016x}")
85}
86
87#[cfg(test)]
88mod tests {
89 use std::path::Path;
90
91 use crate::brutalizer::brutalize_source;
92
93 fn brutalize(source: &str) -> String {
94 brutalize_source(Path::new("test.sol"), source).unwrap()
95 }
96
97 #[test]
98 fn address_cast() {
99 let source = r#"
100pragma solidity ^0.8.0;
101contract T {
102 function f(uint160 x) external pure returns (address) {
103 return address(x);
104 }
105}
106"#;
107 let result = brutalize(source);
108 assert!(result.contains("address(uint160(uint256(uint160(address(x)))"));
109 assert!(result.contains("| (uint256(0x"));
110 assert!(result.contains("<< 160)"));
111 }
112
113 #[test]
114 fn uint8_cast() {
115 let source = r#"
116pragma solidity ^0.8.0;
117contract T {
118 function f(uint256 x) external pure returns (uint8) {
119 return uint8(x);
120 }
121}
122"#;
123 let result = brutalize(source);
124 assert!(result.contains("uint8(uint256(uint8(x)) | (uint256(0x"));
125 assert!(result.contains("<< 8)"));
126 }
127
128 #[test]
129 fn int16_cast() {
130 let source = r#"
131pragma solidity ^0.8.0;
132contract T {
133 function f(int256 x) external pure returns (int16) {
134 return int16(x);
135 }
136}
137"#;
138 let result = brutalize(source);
139 assert!(result.contains("int16(int256(int16(x)) ^ int256(uint256(0x"));
140 assert!(result.contains("<< 16)"));
141 }
142
143 #[test]
144 fn payable_address_cast() {
145 let source = r#"
146pragma solidity ^0.8.0;
147contract T {
148 function f(address x) external pure returns (address payable) {
149 return payable(x);
150 }
151}
152"#;
153 let result = brutalize(source);
154 assert!(result.contains("payable(address(uint160(uint256(uint160(address(x)))"));
155 assert!(result.contains("| (uint256(0x"));
156 assert!(result.contains("<< 160)"));
157 }
158
159 #[test]
160 fn signed_cast_uses_xor() {
161 let source = r#"
162pragma solidity ^0.8.0;
163contract T {
164 function f() external pure returns (int16) {
165 return int16(-1);
166 }
167}
168"#;
169 let result = brutalize(source);
170 assert!(result.contains("int16(int256(int16(-1)) ^ int256(uint256(0x"));
171 assert!(result.contains("<< 16)"));
172 }
173
174 #[test]
175 fn bytes4_cast() {
176 let source = r#"
177pragma solidity ^0.8.0;
178contract T {
179 function f(bytes32 x) external pure returns (bytes4) {
180 return bytes4(x);
181 }
182}
183"#;
184 let result = brutalize(source);
185 assert!(result.contains("bytes4(bytes32(bytes4(x)) | bytes32(uint256(0x"));
186 assert!(result.contains("<< 224"));
187 }
188
189 #[test]
190 fn dirty_mask_is_nonzero_in_effective_width() {
191 assert_eq!(super::dirty_mask("0x0000000000000100", 8), "0x0000000000000001");
192 assert_eq!(super::dirty_mask("0x0000000000000101", 8), "0x0000000000000001");
193 }
194
195 #[test]
196 fn uint256_cast_not_brutalized() {
197 let source = r#"
198pragma solidity ^0.8.0;
199contract T {
200 function f(uint128 x) external pure returns (uint256) {
201 return uint256(x);
202 }
203}
204"#;
205 let result = brutalize(source);
206 assert_eq!(result, source);
207 }
208
209 #[test]
210 fn bool_cast_not_brutalized() {
211 let source = r#"
212pragma solidity ^0.8.0;
213contract T {
214 function f(bool x) external pure returns (bool) {
215 return bool(x);
216 }
217}
218"#;
219 let result = brutalize(source);
220 assert_eq!(result, source);
221 }
222
223 #[test]
224 fn multiple_casts_in_one_function() {
225 let source = r#"
226pragma solidity ^0.8.0;
227contract T {
228 function f(uint256 a, uint256 b) external pure returns (uint8, uint16) {
229 return (uint8(a), uint16(b));
230 }
231}
232"#;
233 let result = brutalize(source);
234 assert!(result.contains("uint8(uint256(uint8(a)) | (uint256(0x"));
235 assert!(result.contains("uint16(uint256(uint16(b)) | (uint256(0x"));
236 }
237}