Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add structure repacker #14

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 124 additions & 6 deletions src/scanners/implementations/struct_repacker.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,132 @@
use syn_solidity::Item;
use syn_solidity::{Item, ItemContract, ItemStruct, Spanned, VariableDeclaration};

use crate::scanners::{result::Reporter, Scanner};
use crate::scanners::{
result::{Reporter, Severity},
Scanner,
};

const BIN_SIZE: u16 = 256;

#[allow(dead_code)]
#[derive(Clone, Debug)]
struct BinItem {
name: String,
size: u16,
}

impl From<&VariableDeclaration> for BinItem {
fn from(field: &VariableDeclaration) -> Self {
BinItem {
name: field.name.as_ref().unwrap().as_string(),
size: match field.ty {
syn_solidity::Type::Address(_, _) => 20,
syn_solidity::Type::Bool(_) => 1,
syn_solidity::Type::String(_) => 32,
syn_solidity::Type::Bytes(_) => 32,
syn_solidity::Type::FixedBytes(_, s) => s.get(),
syn_solidity::Type::Int(_, s) => s.unwrap().get(),
syn_solidity::Type::Uint(_, s) => s.unwrap().get(),
syn_solidity::Type::Array(_) => 32,
syn_solidity::Type::Tuple(_) => 0, // todo!()
syn_solidity::Type::Function(_) => 0, // todo!()
syn_solidity::Type::Mapping(_) => 32,
syn_solidity::Type::Custom(_) => 0, // todo!()
},
}
}
}

trait RepackStrategy {
fn repack(&self, initial_structure: &[BinItem]) -> Vec<BinItem>;
}

#[derive(Default)]
struct FirstFitDecreasing {}

impl RepackStrategy for FirstFitDecreasing {
fn repack(&self, initial_structure: &[BinItem]) -> Vec<BinItem> {
let mut cpy = initial_structure.to_vec();

// Reorder structure fields from biggest size to smallest.
cpy.sort_by(|a, b| b.size.cmp(&a.size));

cpy
}
}

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

impl StructRepacker {
fn scan_contract(&self, contract: &ItemContract, reporter: &mut Reporter) {
for item in &contract.body {
if let Item::Struct(structure) = item {
self.scan_struct(structure, reporter)
}
}
}

fn scan_struct(&self, structure: &ItemStruct, reporter: &mut Reporter) {
let initial_structure: Vec<BinItem> = structure.fields.iter().map(BinItem::from).collect();
let initial_slot_consumption = self.compute_actual_slot_consumption(&initial_structure);
let optimum_slot_consumption = self.compute_optimum_slot_consumption(&initial_structure);

if initial_slot_consumption != optimum_slot_consumption {
let strategy = FirstFitDecreasing::default();
let _repacked_struct = strategy.repack(&initial_structure);

let line = structure.span().start().line;
let column = structure.span().start().column;

reporter.report(
line,
column,
Severity::Boost,
"Structure can be re-written to potentially consume less storage slots",
)
}
}

fn compute_optimum_slot_consumption(&self, structure: &[BinItem]) -> u16 {
(structure.iter().map(|f| f.size).sum::<u16>() as f64 / BIN_SIZE as f64) as u16
}

fn compute_actual_slot_consumption(&self, structure: &Vec<BinItem>) -> u16 {
let mut slot_consumption: u16 = 0;
let mut carry: u16 = 0;

for field in structure {
match carry + field.size {
slot if slot < BIN_SIZE => {
carry += field.size;
}
slot if slot == BIN_SIZE => {
slot_consumption += 1;
carry = 0;
}
_ => {
slot_consumption += 1;
carry = field.size;
}
}
}

if carry != 0 {
slot_consumption += 1
}

slot_consumption
}
}

impl Scanner for StructRepacker {
fn execute(&self, _ast: &[Item], _reporter: &mut Reporter) {
/*
println!("todo!")
*/
fn execute(&self, ast: &[Item], reporter: &mut Reporter) {
for item in ast {
match item {
Item::Contract(contract) => self.scan_contract(contract, reporter),
Item::Struct(structure) => self.scan_struct(structure, reporter),
_ => {}
}
}
}
}
9 changes: 9 additions & 0 deletions tests/contracts/StructsToRepack.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.15;

struct SimpleStruct {
uint128 a;
uint256 b;
uint128 c;
}