Skip to main content

forge/brutalizer/
value.rs

1use 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}