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