Skip to content

Commit a05ad96

Browse files
authored
fix: determine size of unlinked contracts (#3383)
1 parent cfeed67 commit a05ad96

File tree

1 file changed

+66
-46
lines changed

1 file changed

+66
-46
lines changed

common/src/compile.rs

+66-46
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use crate::{term, TestFunctionExt};
33
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, *};
44
use ethers_solc::{
5-
artifacts::{ContractBytecodeSome, Source, Sources},
5+
artifacts::{BytecodeObject, Contract, ContractBytecodeSome, Source, Sources},
66
report::NoReporter,
77
Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, Solc,
88
};
@@ -67,8 +67,6 @@ impl ProjectCompiler {
6767
where
6868
F: FnOnce(&Project) -> eyre::Result<ProjectCompileOutput>,
6969
{
70-
let ProjectCompiler { print_sizes, print_names } = self;
71-
7270
if !project.paths.has_input_files() {
7371
println!("Nothing to compile");
7472
// nothing to do here
@@ -88,60 +86,62 @@ impl ProjectCompiler {
8886
eyre::bail!(output.to_string())
8987
} else if output.is_unchanged() {
9088
println!("No files changed, compilation skipped");
89+
self.handle_output(&output);
9190
} else {
9291
// print the compiler output / warnings
9392
println!("{output}");
9493

95-
// print any sizes or names
96-
if print_names {
97-
let compiled_contracts = output.compiled_contracts_by_compiler_version();
98-
for (version, contracts) in compiled_contracts.into_iter() {
99-
println!(
100-
" compiler version: {}.{}.{}",
101-
version.major, version.minor, version.patch
102-
);
103-
for (name, _) in contracts {
104-
println!(" - {name}");
105-
}
94+
self.handle_output(&output);
95+
}
96+
97+
Ok(output)
98+
}
99+
100+
/// If configured, this will print sizes or names
101+
fn handle_output(&self, output: &ProjectCompileOutput) {
102+
// print any sizes or names
103+
if self.print_names {
104+
let compiled_contracts = output.compiled_contracts_by_compiler_version();
105+
for (version, contracts) in compiled_contracts.into_iter() {
106+
println!(
107+
" compiler version: {}.{}.{}",
108+
version.major, version.minor, version.patch
109+
);
110+
for (name, _) in contracts {
111+
println!(" - {name}");
106112
}
107113
}
108-
if print_sizes {
109-
// add extra newline if names were already printed
110-
if print_names {
111-
println!();
112-
}
113-
let compiled_contracts = output.compiled_contracts_by_compiler_version();
114-
let mut size_report = SizeReport { contracts: BTreeMap::new() };
115-
for (_, contracts) in compiled_contracts.into_iter() {
116-
for (name, contract) in contracts {
117-
let size = contract
118-
.get_deployed_bytecode_bytes()
119-
.map(|bytes| bytes.0.len())
120-
.unwrap_or_default();
121-
122-
let dev_functions =
123-
contract.abi.as_ref().unwrap().abi.functions().into_iter().filter(
124-
|func| {
125-
func.name.is_test() ||
126-
func.name.eq("IS_TEST") ||
127-
func.name.eq("IS_SCRIPT")
128-
},
129-
);
130-
131-
let is_dev_contract = dev_functions.into_iter().count() > 0;
132-
size_report.contracts.insert(name, ContractInfo { size, is_dev_contract });
133-
}
114+
}
115+
if self.print_sizes {
116+
// add extra newline if names were already printed
117+
if self.print_names {
118+
println!();
119+
}
120+
let compiled_contracts = output.compiled_contracts_by_compiler_version();
121+
let mut size_report = SizeReport { contracts: BTreeMap::new() };
122+
for (_, contracts) in compiled_contracts.into_iter() {
123+
for (name, contract) in contracts {
124+
let size = deployed_contract_size(&contract).unwrap_or_default();
125+
126+
let dev_functions =
127+
contract.abi.as_ref().unwrap().abi.functions().into_iter().filter(|func| {
128+
func.name.is_test() ||
129+
func.name.eq("IS_TEST") ||
130+
func.name.eq("IS_SCRIPT")
131+
});
132+
133+
let is_dev_contract = dev_functions.into_iter().count() > 0;
134+
size_report.contracts.insert(name, ContractInfo { size, is_dev_contract });
134135
}
136+
}
135137

136-
println!("{size_report}");
138+
println!("{size_report}");
137139

138-
// exit with error if any contract exceeds the size limit, excluding test contracts.
139-
let exit_status = size_report.exceeds_size_limit().into();
140-
std::process::exit(exit_status);
140+
// exit with error if any contract exceeds the size limit, excluding test contracts.
141+
if size_report.exceeds_size_limit() {
142+
std::process::exit(1);
141143
}
142144
}
143-
144-
Ok(output)
145145
}
146146
}
147147

@@ -203,7 +203,27 @@ impl Display for SizeReport {
203203
}
204204
}
205205

206+
/// Returns the size of the deployed contract
207+
pub fn deployed_contract_size(contract: &Contract) -> Option<usize> {
208+
let bytecode = contract.get_deployed_bytecode_object()?;
209+
let size = match bytecode.as_ref() {
210+
BytecodeObject::Bytecode(bytes) => bytes.len(),
211+
BytecodeObject::Unlinked(unlinked) => {
212+
// we don't need to account for placeholders here, because library placeholders take up
213+
// 40 characters: `__$<library hash>$__` which is the same as a 20byte address in hex.
214+
let mut size = unlinked.as_bytes().len();
215+
if unlinked.starts_with("0x") {
216+
size -= 2;
217+
}
218+
// hex -> bytes
219+
size / 2
220+
}
221+
};
222+
Some(size)
223+
}
224+
206225
/// How big the contract is and whether it is a dev contract where size limits can be neglected
226+
#[derive(Debug, Clone, Copy)]
207227
pub struct ContractInfo {
208228
/// size of the contract in bytes
209229
pub size: usize,

0 commit comments

Comments
 (0)