solstat/analyzer/optimizations/
pack_struct_variables.rs

1use std::collections::HashSet;
2
3use solang_parser::pt::{ContractPart, Loc, StructDefinition};
4use solang_parser::{self, pt::SourceUnit, pt::SourceUnitPart};
5
6use crate::analyzer::ast::{self, Target};
7use crate::analyzer::utils;
8
9///Identifiy opportunities to pack structs to save gas
10pub fn pack_struct_variables_optimization(source_unit: SourceUnit) -> HashSet<Loc> {
11    let mut optimization_locations: HashSet<Loc> = HashSet::new();
12
13    let target_nodes = ast::extract_target_from_node(Target::StructDefinition, source_unit.into());
14
15    for node in target_nodes {
16        if node.is_source_unit_part() {
17            if let SourceUnitPart::StructDefinition(struct_definition) =
18                node.source_unit_part().unwrap()
19            {
20                let struct_location = struct_definition.loc;
21                if struct_can_be_packed(*struct_definition) {
22                    optimization_locations.insert(struct_location);
23                }
24            }
25        } else if node.is_contract_part() {
26            if let ContractPart::StructDefinition(struct_definition) = node.contract_part().unwrap()
27            {
28                let struct_location = struct_definition.loc;
29                if struct_can_be_packed(*struct_definition) {
30                    optimization_locations.insert(struct_location);
31                }
32            }
33        }
34    }
35    optimization_locations
36}
37
38fn struct_can_be_packed(struct_definition: StructDefinition) -> bool {
39    let mut variable_sizes: Vec<u16> = vec![];
40
41    for variable_declaration in struct_definition.fields {
42        variable_sizes.push(utils::get_type_size(variable_declaration.ty));
43    }
44
45    //create an unordered list of variable sizes
46    let unordered_variable_sizes = variable_sizes.clone();
47
48    //Sort the variable sizes
49    variable_sizes.sort();
50
51    //If the ordered version is smaller than the unordered
52    utils::storage_slots_used(unordered_variable_sizes) > utils::storage_slots_used(variable_sizes)
53}
54
55#[test]
56fn test_pack_struct_variables_optimization() {
57    let file_contents = r#"
58
59    //should not match
60    struct Ex {
61        uint256 spotPrice;
62        uint128 res0;
63        uint128 res1;
64    }
65
66    //should match
67    struct Ex1 {
68        bool isUniV2;
69        bytes32 salt;
70        bytes16 initBytecode;
71    }
72    
73
74contract OrderRouter {
75  
76    
77    //should not match
78    struct Ex2 {
79        bool isUniV2;
80        address factoryAddress;
81        bytes16 initBytecode;
82    }
83
84    //should match
85    struct Ex3 {
86        bool isUniV2;
87        bytes32 salt;
88        bytes16 initBytecode;
89    }
90
91    //should not match
92    struct Ex4 {
93        bytes16 initBytecode;
94        bool isUniV2;
95        address factoryAddress;
96    }
97
98    //should not match
99    struct Ex5 {
100        bool isUniV2;
101        bytes16 initBytecode;
102        address factoryAddress;
103    }
104
105    //should match
106    struct Ex6 {
107        uint128 thing3;
108        uint256 thing1;
109        uint128 thing2;
110    }
111
112    // Should match
113    struct Ex7 {
114        address owner; // 160 bits
115        uint256 num0;  // 256 bits
116        bytes4 b0;     // 32 bits
117        uint64 num1;   // 64 bits
118    }
119}
120    "#;
121
122    let source_unit = solang_parser::parse(file_contents, 0).unwrap().0;
123
124    let optimization_locations = pack_struct_variables_optimization(source_unit);
125
126    assert_eq!(optimization_locations.len(), 4)
127}