-
Notifications
You must be signed in to change notification settings - Fork 30
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: checks on upper bounds of contract storage sizes #169
base: main
Are you sure you want to change the base?
Changes from all commits
9bebce1
16e28d4
0ba5988
23122d5
6e6c78c
73580a6
273ef23
1de1e60
3f85d3b
e83fc13
05a395e
b44c589
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
use crate::{ | ||
ast_lowering::resolve::{Declaration, Declarations}, | ||
hir::{self, Res}, | ||
ty::{Gcx, Ty}, | ||
ty::{Gcx, Ty, TyKind}, | ||
}; | ||
use alloy_primitives::U256; | ||
use rayon::prelude::*; | ||
use solar_data_structures::{map::FxHashSet, parallel}; | ||
|
||
|
@@ -11,13 +12,91 @@ pub(crate) fn check(gcx: Gcx<'_>) { | |
gcx.sess, | ||
gcx.hir.par_contract_ids().for_each(|id| { | ||
check_duplicate_definitions(gcx, &gcx.symbol_resolver.contract_scopes[id]); | ||
check_storage_size_upper_bound(gcx, id); | ||
}), | ||
gcx.hir.par_source_ids().for_each(|id| { | ||
check_duplicate_definitions(gcx, &gcx.symbol_resolver.source_scopes[id]); | ||
}), | ||
); | ||
} | ||
|
||
/// Checks for violation of maximum storage size to ensure slot allocation algorithms works. | ||
/// Reference: https://github.com/ethereum/solidity/blob/03e2739809769ae0c8d236a883aadc900da60536/libsolidity/analysis/ContractLevelChecker.cpp#L556C1-L570C2 | ||
fn check_storage_size_upper_bound(gcx: Gcx<'_>, contract_id: hir::ContractId) { | ||
let contract_span = gcx.hir.contract(contract_id).span; | ||
let contract_items = gcx.hir.contract_items(contract_id); | ||
let mut total_size = U256::ZERO; | ||
for item in contract_items { | ||
if let hir::Item::Variable(variable) = item { | ||
// Skip constant and immutable variables | ||
if variable.mutability.is_none() { | ||
let t = gcx.type_of_hir_ty(&variable.ty); | ||
match ty_upper_bound_storage_var_size(t, gcx) | ||
.and_then(|size_contribution| total_size.checked_add(size_contribution)) | ||
{ | ||
Some(sz) => { | ||
total_size = sz; | ||
} | ||
None => { | ||
gcx.dcx() | ||
.err("contract requires too much storage") | ||
.span(contract_span) | ||
.emit(); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if gcx.sess.storage_sz_ub { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's call it like "print-contract-storage-sizes" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only check the upper bound of storage size, so I'm afraid that wouldn't be accurate depiction of what users are looking for. for example:
Currently, this totals to "2", which is less than 2^256 so passes the check. (this check is pre-condition to make sure the slot allocation algorithm will work properly later) Whereas someone wanting to print storage size would expect 128 + 128 = 256 bits so If we go with this, do we make another flag for the actual contract storage size then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DaniPopes isn't this more like "print-contract-max-storage-sizes" ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This probably will go with the actual storage implementation later on, the "storage-layout" output |
||
let full_contract_name = format!("{}", gcx.contract_fully_qualified_name(contract_id)); | ||
eprintln!("{full_contract_name} requires {total_size} maximum storage"); | ||
} | ||
} | ||
|
||
fn ty_upper_bound_storage_var_size(ty: Ty<'_>, gcx: Gcx<'_>) -> Option<U256> { | ||
match ty.kind { | ||
TyKind::Elementary(..) | ||
| TyKind::StringLiteral(..) | ||
| TyKind::IntLiteral(..) | ||
| TyKind::Mapping(..) | ||
| TyKind::Contract(..) | ||
| TyKind::Udvt(..) | ||
| TyKind::Enum(..) | ||
| TyKind::DynArray(..) => Some(U256::from(1)), | ||
TyKind::Ref(..) | ||
| TyKind::Tuple(..) | ||
| TyKind::FnPtr(..) | ||
| TyKind::Module(..) | ||
| TyKind::BuiltinModule(..) | ||
| TyKind::Event(..) | ||
| TyKind::Meta(..) | ||
| TyKind::Err(..) | ||
| TyKind::Error(..) => { | ||
unreachable!() | ||
} | ||
TyKind::Array(ty, uint) => { | ||
// Reference: https://github.com/ethereum/solidity/blob/03e2739809769ae0c8d236a883aadc900da60536/libsolidity/ast/Types.cpp#L1800C1-L1806C2 | ||
let elem_size = ty_upper_bound_storage_var_size(ty, gcx)?; | ||
uint.checked_mul(elem_size) | ||
} | ||
TyKind::Struct(struct_id) => { | ||
let strukt = gcx.hir.strukt(struct_id); | ||
// Reference https://github.com/ethereum/solidity/blob/03e2739809769ae0c8d236a883aadc900da60536/libsolidity/ast/Types.cpp#L2303C1-L2309C2 | ||
let mut total_size = U256::from(1); | ||
for field_id in strukt.fields { | ||
let variable = gcx.hir.variable(*field_id); | ||
let t = gcx.type_of_hir_ty(&variable.ty); | ||
let size_contribution = ty_upper_bound_storage_var_size(t, gcx)?; | ||
total_size = total_size.checked_add(size_contribution)?; | ||
} | ||
Some(total_size) | ||
} | ||
TyKind::Type(ty) => ty_upper_bound_storage_var_size(ty, gcx), | ||
} | ||
} | ||
|
||
/// Checks for definitions that have the same name and parameter types in the given scope. | ||
fn check_duplicate_definitions(gcx: Gcx<'_>, scope: &Declarations) { | ||
let is_duplicate = |a: Declaration, b: Declaration| -> bool { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
//@ compile-flags: -Zstorage-sz-ub | ||
|
||
struct Person { | ||
string name; | ||
uint age; | ||
} | ||
|
||
contract B { | ||
uint c; | ||
bool x; | ||
} | ||
|
||
// Total = 1 + 1 = 2 | ||
|
||
contract A { | ||
uint256 a; // 1 | ||
bool b; // 1 | ||
B c; // 1 | ||
Person e; // 1 + 2 fields = 3 | ||
int128 f; // 1 | ||
Person[] g; // 1 | ||
Person[23] h; // 23 * 3 = 69 | ||
} | ||
|
||
// Total = 1 + 1 + 1 + 3 + 1 + 1 + 69 = 77 | ||
|
||
contract M { | ||
struct P1 { | ||
string first; | ||
string middle; | ||
string last; | ||
} | ||
|
||
P1 my; // 4 | ||
mapping(string => uint256) public a; // 1 | ||
P1[] public b; // 1 | ||
bool c; // 1 | ||
B d; // 1 | ||
} | ||
|
||
// Total = 4 + 1 + 1 + 1 + 1 = 8 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ROOT/tests/ui/typeck/contract_storage_size_check.sol:B requires 2 maximum storage | ||
ROOT/tests/ui/typeck/contract_storage_size_check.sol:A requires 77 maximum storage | ||
ROOT/tests/ui/typeck/contract_storage_size_check.sol:M requires 8 maximum storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please move to above help