Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

## Unreleased

## [1.5.14] - 2026-02-03
- **Breaking**: Enforce EIP-170 contract size limit (24,576 bytes) by default.
- Compilation now fails if the runtime bytecode exceeds the EIP-170 limit.
- Use `--no-size-limit` flag to bypass this check.
- Increase maximum loop iterations from 10,000 to 100,000.

## [1.5.13] - 2026-01-18
- Add support for `true` and `false` keywords as constant override values via `-c` CLI flag.
- Example: `hnc test.huff -c DEBUG=true`
Expand Down
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/cakevm/huff-neo"
rust-version = "1.89"
version = "1.5.13"
version = "1.5.14"

[workspace.dependencies]
huff-neo-codegen = { path = "crates/codegen" }
Expand Down
4 changes: 4 additions & 0 deletions bin/hnc/src/arguments/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ pub struct HuffArgs {
#[clap(short = 'e', long = "evm-version")]
pub evm_version: Option<String>,

/// Skip the contract size limit check (EIP-170: 24576 bytes)
#[clap(long = "no-size-limit")]
pub no_size_limit: bool,

/// Test subcommand
#[clap(subcommand)]
pub test: Option<TestCommands>,
Expand Down
1 change: 1 addition & 0 deletions bin/hnc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ fn main() {
cached: use_cache,
file_provider: Arc::new(FileSystemFileProvider {}),
relax_jumps: cli.relax_jumps,
no_size_limit: cli.no_size_limit,
};

// Handle flattened source output
Expand Down
12 changes: 12 additions & 0 deletions book/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Options:
-t, --alt-constructor <ALT_CONSTRUCTOR> Compile a specific constructor macro
-e, --evm-version <EVM_VERSION> Set the EVM version [default: osaka]
--flattened-source Output the flattened source code with all dependencies resolved
--no-size-limit Skip the contract size limit check (EIP-170: 24576 bytes)
-V, --version Print version
-h, --help Print help
```
Expand Down Expand Up @@ -122,6 +123,17 @@ Example:
hnc ./src/ERC20.huff --flattened-source
```

### `--no-size-limit`

Passing the `--no-size-limit` flag bypasses the EIP-170 contract size limit check.
By default, compilation fails if the runtime bytecode exceeds 24,576 bytes (the
maximum deployable contract size on Ethereum mainnet).

Example:
```shell
hnc ./src/LargeContract.huff -b --no-size-limit
```

### `-g` Interface

Passing the `-g` flag will generate a Solidity interface for the Huff contract
Expand Down
2 changes: 1 addition & 1 deletion crates/codegen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn example() {
let main_bytecode = "5f3560e01c8063a9059cbb1461004757806340c10f19146100d757806370a082311461014157806318160ddd1461015c578063095ea7b314610166578063dd62ed3e1461017d575b600435336024358160016000526000602001526040600020548082116100d3578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa360015f5260205ff35b5f5ffd5b5f5433146100e3575f5ffd5b6004355f60243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa35b60043560016000526000602001526040600020545f5260205ff35b6002545f5260205ff35b602435600435336000526000602001526040600020555b6024356004356000526000602001526040600020545f5260205ff3";
let constructor_bytecode = "335f55";
let inputs = vec![];
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None);
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None, false);

// Validate the output bytecode
assert_eq!(churn_res.unwrap().bytecode, "335f5561019980600d3d393df35f3560e01c8063a9059cbb1461004757806340c10f19146100d757806370a082311461014157806318160ddd1461015c578063095ea7b314610166578063dd62ed3e1461017d575b600435336024358160016000526000602001526040600020548082116100d3578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa360015f5260205ff35b5f5ffd5b5f5433146100e3575f5ffd5b6004355f60243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa35b60043560016000526000602001526040600020545f5260205ff35b6002545f5260205ff35b602435600435336000526000602001526040600020555b6024356004356000526000602001526040600020545f5260205ff3".to_lowercase());
Expand Down
2 changes: 1 addition & 1 deletion crates/codegen/src/irgen/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ pub fn statement_gen<'a>(
}

// Maximum iterations safety check
const MAX_LOOP_ITERATIONS: usize = 10_000;
const MAX_LOOP_ITERATIONS: usize = 100_000;
let mut current = start_u256;
let mut iteration_count = 0;

Expand Down
14 changes: 14 additions & 0 deletions crates/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub(crate) const MAX_MACRO_RECURSION_DEPTH: usize = 100;
/// though typical contracts converge in a few iterations.
const JUMP_RELAXATION_MAX_ITERATIONS: usize = 20;

/// EIP-170 contract size limit (24576 bytes)
pub const EIP170_CONTRACT_SIZE_LIMIT: usize = 24576;

/// Compiles Huff contracts into EVM bytecode.
///
/// Transforms a Contract AST into executable bytecode, handling macro expansion,
Expand Down Expand Up @@ -1133,6 +1136,7 @@ impl Codegen {
/// * `args` - A vector of Tokens representing constructor arguments
/// * `main_bytecode` - The compiled MAIN Macro bytecode
/// * `constructor_bytecode` - The compiled `CONSTRUCTOR` Macro bytecode
/// * `no_size_limit` - Skip the EIP-170 contract size limit check
#[allow(clippy::too_many_arguments)]
pub fn churn(
&mut self,
Expand All @@ -1143,6 +1147,7 @@ impl Codegen {
has_custom_bootstrap: bool,
main_source_map: Option<Vec<SourceMapEntry>>,
constructor_source_map: Option<Vec<SourceMapEntry>>,
no_size_limit: bool,
) -> Result<Artifact, CodegenError> {
let artifact: &mut Artifact = if let Some(art) = &mut self.artifact {
art
Expand All @@ -1157,6 +1162,15 @@ impl Codegen {
let contract_length = main_bytecode.len() / 2;
let constructor_length = constructor_bytecode.len() / 2;

// Check contract size limit (EIP-170)
if !no_size_limit && contract_length > EIP170_CONTRACT_SIZE_LIMIT {
return Err(CodegenError {
kind: CodegenErrorKind::ContractSizeLimitExceeded(contract_length, EIP170_CONTRACT_SIZE_LIMIT),
span: AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }]).boxed(),
token: None,
});
}

// Sort constructor arguments so that statically sized args are inserted last.
args.sort_by(|a, b| {
if a.is_dynamic() && !b.is_dynamic() {
Expand Down
6 changes: 3 additions & 3 deletions crates/codegen/tests/churn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fn churns_into_bytecode() {
// ERC20 Bytecode
let main_bytecode = "60003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
let constructor_bytecode = "33600055";
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None);
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None, false);
assert!(churn_res.is_ok());
assert_eq!(churn_res.unwrap().bytecode, "336000556101ac80600e3d393df360003560e01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());

Expand All @@ -41,7 +41,7 @@ fn churns_custom_bootstrap() {
let main_bytecode = "6001600216";
// custom bootstrap code
let constructor_bytecode = "64600160020160005260056000f3";
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, true, None, None);
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, true, None, None, false);

assert!(churn_res.is_ok());
assert_eq!(churn_res.unwrap().bytecode, "64600160020160005260056000f36001600216".to_lowercase());
Expand All @@ -61,7 +61,7 @@ fn churns_constructor_args() {
// ERC20 Bytecode
let main_bytecode = "60003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3";
let constructor_bytecode = "33600055";
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None);
let churn_res = cg.churn(Arc::new(FileSource::default()), inputs, main_bytecode, constructor_bytecode, false, None, None, false);
assert!(churn_res.is_ok());
assert_ne!(churn_res.unwrap().bytecode, "336000556101ac806100116000396000f360003560E01c8063a9059cbb1461004857806340c10f19146100de57806370a082311461014e57806318160ddd1461016b578063095ea7b314610177578063dd62ed3e1461018e575b600435336024358160016000526000602001526040600020548082116100d8578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a3600160005260206000f35b60006000fd5b60005433146100ed5760006000fd5b600435600060243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002556000527fDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF60206000a35b600435600160005260006020015260406000205460005260206000f35b60025460005260206000f35b602435600435336000526000602001526040600020555b60243560043560005260006020015260406000205460005260206000f3".to_lowercase());
}
6 changes: 3 additions & 3 deletions crates/core/benches/huff_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ fn codegen_erc20_benchmark(c: &mut Criterion) {
// Churn
let mut cg = Codegen::new();
let artifact =
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None).unwrap();
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None, false).unwrap();

// Full expected bytecode output (generated from huff-neo)
let expected_bytecode = "335f5561019980600d3d393df35f3560e01c8063a9059cbb1461004757806340c10f19146100d757806370a082311461014157806318160ddd1461015c578063095ea7b314610166578063dd62ed3e1461017d575b600435336024358160016000526000602001526040600020548082116100d3578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa360015f5260205ff35b5f5ffd5b5f5433146100e3575f5ffd5b6004355f60243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa35b60043560016000526000602001526040600020545f5260205ff35b6002545f5260205ff35b602435600435336000526000602001526040600020555b6024356004356000526000602001526040600020545f5260205ff3";
Expand Down Expand Up @@ -144,7 +144,7 @@ fn erc20_compilation_benchmark(c: &mut Criterion) {
// Churn
let mut cg = Codegen::new();
let artifact =
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None).unwrap();
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None, false).unwrap();

// Full expected bytecode output (generated from huff-neo)
let expected_bytecode = "335f5561019980600d3d393df35f3560e01c8063a9059cbb1461004757806340c10f19146100d757806370a082311461014157806318160ddd1461015c578063095ea7b314610166578063dd62ed3e1461017d575b600435336024358160016000526000602001526040600020548082116100d3578190038260016000526000602001526040600020558281906001600052600060200152604060002054018360016000526000602001526040600020555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa360015f5260205ff35b5f5ffd5b5f5433146100e3575f5ffd5b6004355f60243582819060016000526000602001526040600020540183600160005260006020015260406000205580600254016002555f527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa35b60043560016000526000602001526040600020545f5260205ff35b6002545f5260205ff35b602435600435336000526000602001526040600020555b6024356004356000526000602001526040600020545f5260205ff3";
Expand Down Expand Up @@ -192,7 +192,7 @@ fn erc721_compilation_benchmark(c: &mut Criterion) {
// Churn
let mut cg = Codegen::new();
let artifact =
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None).unwrap();
cg.churn(file_source.clone(), vec![], &main_bytecode, &constructor_bytecode, has_custom_bootstrap, None, None, false).unwrap();

// Full expected bytecode output (generated from huff-neo)
let expected_bytecode = "335f5561038680600d3d393df35f3560e01c8063a9059cbb1461009f57806342842e0e1461019e578063b88d4fde146101a2578063095ea7b314610267578063a22cb465146102f6578063081812fc146102db57806340c10f19146101a657806370a082311461024c5780636352211e1461036b57806306fdde031461033f57806395d89b4114610343578063c87b56dd1461034757806301ffc9a71461034b578063e985e9c51461034f575b6044356024356004358083600160005260006020015260406000205491146100c65761019a565b8033146100ff5733816000526000602001526040600020546100ff5782600260005260006020015260406000205433146100ff5761019a565b600181600360005260006020015260406000205403816003600052600060200152604060002055816003600052600060200152604060002054600101826003600052600060200152604060002055818360016000526000602001526040600020555f8360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60205fa4005b5f5ffd5b5f5ffd5b5f5ffd5b5f5433146101b2575f5ffd5b6024356004355f826001600052600060200152604060002054156101d557610248565b816003600052600060200152604060002054600101826003600052600060200152604060002055818360016000526000602001526040600020555f8360026000526000602001526040600020557fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f5fa4005b5f5ffd5b60043560036000526000602001526040600020545f5260205ff35b60243580600160005260006020015260406000205480331433826000526000602001526040600020541761029a576102d7565b60043580836002600052600060200152604060002055907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f5fa4005b5f5ffd5b60043560026000526000602001526040600020545f5260205ff35b60243560043533600052600060200152604060002055600435336024355f527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c315f5fa4005b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b6024356004356000526000602001526040600020545f5260205ff35b60043560016000526000602001526040600020545f5260205ff3";
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub struct Compiler<'a, 'l> {
pub file_provider: Arc<dyn FileProvider>,
/// Whether to apply jump relaxation optimization
pub relax_jumps: bool,
/// Skip the contract size limit validation (EIP-170: 24576 bytes)
pub no_size_limit: bool,
}

impl<'a, 'l> Compiler<'a, 'l> {
Expand Down Expand Up @@ -115,6 +117,7 @@ impl<'a, 'l> Compiler<'a, 'l> {
cached,
file_provider: Arc::new(FileSystemFileProvider {}),
relax_jumps: false,
no_size_limit: false,
}
}

Expand Down Expand Up @@ -146,6 +149,7 @@ impl<'a, 'l> Compiler<'a, 'l> {
cached: false,
file_provider: Arc::new(InMemoryFileProvider::new(file_sources)),
relax_jumps: false,
no_size_limit: false,
}
}

Expand Down Expand Up @@ -559,6 +563,7 @@ impl<'a, 'l> Compiler<'a, 'l> {
has_custom_bootstrap,
Some(main_source_map),
Some(constructor_source_map),
self.no_size_limit,
);
match churn_res {
Ok(mut artifact) => {
Expand Down
12 changes: 10 additions & 2 deletions crates/core/tests/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,16 @@ fn test_dyn_constructor_arg_builtin() {
let main_code = Codegen::generate_main_bytecode(evm_version, &contract, None, false).unwrap();

let args = Codegen::encode_constructor_args(vec![String::from("testing")]);
let final_bytecode =
cg.churn(Arc::new(FileSource::default()), args, main_code.as_str(), constructor_code.as_str(), has_custom_bootstrap, None, None);
let final_bytecode = cg.churn(
Arc::new(FileSource::default()),
args,
main_code.as_str(),
constructor_code.as_str(),
has_custom_bootstrap,
None,
None,
false,
);

assert_eq!(
final_bytecode.unwrap().bytecode,
Expand Down
Loading
Loading