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