Skip to content

Commit a9f1d54

Browse files
committed
feat(scan): implement ffd strategy for struct repacking
Signed-off-by: Luca Georges Francois <[email protected]>
1 parent 4d7c72a commit a9f1d54

File tree

1 file changed

+124
-6
lines changed

1 file changed

+124
-6
lines changed
Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,132 @@
1-
use syn_solidity::Item;
1+
use syn_solidity::{Item, ItemContract, ItemStruct, VariableDeclaration};
22

3-
use crate::scanners::{result::Reporter, Scanner};
3+
use crate::scanners::{
4+
result::{Reporter, Severity},
5+
Scanner,
6+
};
7+
8+
const BIN_SIZE: u16 = 256;
9+
10+
#[allow(dead_code)]
11+
#[derive(Clone, Debug)]
12+
struct BinItem {
13+
name: String,
14+
size: u16,
15+
}
16+
17+
impl From<&VariableDeclaration> for BinItem {
18+
fn from(field: &VariableDeclaration) -> Self {
19+
BinItem {
20+
name: field.name.as_ref().unwrap().as_string(),
21+
size: match field.ty {
22+
syn_solidity::Type::Address(_, _) => 20,
23+
syn_solidity::Type::Bool(_) => 1,
24+
syn_solidity::Type::String(_) => 32,
25+
syn_solidity::Type::Bytes(_) => 32,
26+
syn_solidity::Type::FixedBytes(_, s) => s.get(),
27+
syn_solidity::Type::Int(_, s) => s.unwrap().get(),
28+
syn_solidity::Type::Uint(_, s) => s.unwrap().get(),
29+
syn_solidity::Type::Array(_) => 32,
30+
syn_solidity::Type::Tuple(_) => todo!(),
31+
syn_solidity::Type::Function(_) => todo!(),
32+
syn_solidity::Type::Mapping(_) => 32,
33+
syn_solidity::Type::Custom(_) => todo!(),
34+
},
35+
}
36+
}
37+
}
38+
39+
trait RepackStrategy {
40+
fn repack(&self, initial_structure: &[BinItem]) -> Vec<BinItem>;
41+
}
42+
43+
#[derive(Default)]
44+
struct FirstFitDecreasing {}
45+
46+
impl RepackStrategy for FirstFitDecreasing {
47+
fn repack(&self, initial_structure: &[BinItem]) -> Vec<BinItem> {
48+
let mut cpy = initial_structure.to_vec();
49+
50+
// Reorder structure fields from biggest size to smallest.
51+
cpy.sort_by(|a, b| b.size.cmp(&a.size));
52+
53+
cpy
54+
}
55+
}
456

557
#[derive(Default)]
658
pub struct StructRepacker {}
759

60+
impl StructRepacker {
61+
fn scan_contract(&self, contract: &ItemContract, reporter: &mut Reporter) {
62+
for item in &contract.body {
63+
if let Item::Struct(structure) = item {
64+
self.scan_struct(structure, reporter)
65+
}
66+
}
67+
}
68+
69+
fn scan_struct(&self, structure: &ItemStruct, reporter: &mut Reporter) {
70+
let initial_structure: Vec<BinItem> = structure.fields.iter().map(BinItem::from).collect();
71+
let initial_slot_consumption = self.compute_actual_slot_consumption(&initial_structure);
72+
let optimum_slot_consumption = self.compute_optimum_slot_consumption(&initial_structure);
73+
74+
if initial_slot_consumption != optimum_slot_consumption {
75+
let strategy = FirstFitDecreasing::default();
76+
let _repacked_struct = strategy.repack(&initial_structure);
77+
78+
let line = structure.span().start().line;
79+
let column = structure.span().start().column;
80+
81+
reporter.report(
82+
line,
83+
column,
84+
Severity::Boost,
85+
"Structure can be re-written to potentially consume less storage slots",
86+
)
87+
}
88+
}
89+
90+
fn compute_optimum_slot_consumption(&self, structure: &[BinItem]) -> u16 {
91+
(structure.iter().map(|f| f.size).sum::<u16>() as f64 / BIN_SIZE as f64) as u16
92+
}
93+
94+
fn compute_actual_slot_consumption(&self, structure: &Vec<BinItem>) -> u16 {
95+
let mut slot_consumption: u16 = 0;
96+
let mut carry: u16 = 0;
97+
98+
for field in structure {
99+
match carry + field.size {
100+
slot if slot < BIN_SIZE => {
101+
carry += field.size;
102+
}
103+
slot if slot == BIN_SIZE => {
104+
slot_consumption += 1;
105+
carry = 0;
106+
}
107+
_ => {
108+
slot_consumption += 1;
109+
carry = field.size;
110+
}
111+
}
112+
}
113+
114+
if carry != 0 {
115+
slot_consumption += 1
116+
}
117+
118+
slot_consumption
119+
}
120+
}
121+
8122
impl Scanner for StructRepacker {
9-
fn execute(&self, _ast: &[Item], _reporter: &mut Reporter) {
10-
/*
11-
println!("todo!")
12-
*/
123+
fn execute(&self, ast: &[Item], reporter: &mut Reporter) {
124+
for item in ast {
125+
match item {
126+
Item::Contract(contract) => self.scan_contract(contract, reporter),
127+
Item::Struct(structure) => self.scan_struct(structure, reporter),
128+
_ => {}
129+
}
130+
}
13131
}
14132
}

0 commit comments

Comments
 (0)