|
1 |
| -use syn_solidity::Item; |
| 1 | +use syn_solidity::{Item, ItemContract, ItemStruct, VariableDeclaration}; |
2 | 2 |
|
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 | +} |
4 | 56 |
|
5 | 57 | #[derive(Default)]
|
6 | 58 | pub struct StructRepacker {}
|
7 | 59 |
|
| 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 | + |
8 | 122 | 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 | + } |
13 | 131 | }
|
14 | 132 | }
|
0 commit comments