1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(unused_extern_crates)]
4#![forbid(unsafe_code)]
5
6use crate::irgen::builtin_function::builtin_pad;
7use alloy_dyn_abi::{DynSolType, DynSolValue};
8use alloy_primitives::{U256, hex};
9use huff_neo_utils::ast::huff::*;
10use huff_neo_utils::ast::span::AstSpan;
11use huff_neo_utils::builtin_eval::{PadDirection, eval_builtin_bytes, eval_event_hash, eval_function_signature};
12use huff_neo_utils::bytes_util::str_to_bytes32;
13use huff_neo_utils::file::file_source::FileSource;
14use huff_neo_utils::scope::ScopeManager;
15use huff_neo_utils::{
16 abi::*,
17 artifact::*,
18 bytecode::*,
19 bytes_util,
20 error::CodegenError,
21 opcodes::Opcode,
22 prelude::{CodegenErrorKind, EVMVersion, PushValue, Span, deploy_bootstrap, format_even_bytes, pad_n_bytes},
23 types::EToken,
24};
25use std::{
26 collections::{BTreeMap, HashMap},
27 fs,
28 path::Path,
29 sync::Arc,
30};
31
32mod irgen;
33use crate::irgen::prelude::*;
34
35pub(crate) const MAX_MACRO_RECURSION_DEPTH: usize = 100;
40
41const JUMP_RELAXATION_MAX_ITERATIONS: usize = 20;
46
47const CODESIZE_MAX_ITERATIONS: usize = 10;
55
56fn args_start_span(res: &BytecodeRes) -> Option<&AstSpan> {
58 res.bytes.iter().find_map(|segment| match &segment.bytes {
59 Bytes::ArgsStartPlaceholder(span) => Some(span),
60 _ => None,
61 })
62}
63
64fn converge_codesizes<T, K: PartialEq>(initial: K, mut pass: impl FnMut(&K) -> Result<(T, K), CodegenError>) -> Result<T, CodegenError> {
67 let mut key = initial;
68 for _ in 0..CODESIZE_MAX_ITERATIONS {
69 let (output, next) = pass(&key)?;
70 if next == key {
71 return Ok(output);
72 }
73 key = next;
74 }
75 Err(CodegenError { kind: CodegenErrorKind::CodesizeNotConverged(CODESIZE_MAX_ITERATIONS), span: AstSpan(vec![]).boxed(), token: None })
76}
77
78#[derive(Debug, Default, Clone)]
88pub struct Codegen {
89 pub ast: Option<Contract>,
91 pub artifact: Option<Artifact>,
93 pub main_bytecode: Option<String>,
95 pub constructor_bytecode: Option<String>,
97}
98
99#[derive(Debug, Default, Clone)]
104pub struct TableBytecodeOutput {
105 pub body: String,
107 pub tables: String,
110 pub local_table_offsets: HashMap<String, usize>,
112 pub source_map: Vec<SourceMapEntry>,
114}
115
116#[derive(Debug, Default, Clone)]
122pub struct MainBytecodeOutput {
123 pub bytecode: String,
125 pub source_map: Vec<SourceMapEntry>,
127 pub table_offsets: HashMap<String, usize>,
129}
130
131impl MainBytecodeOutput {
132 pub fn body_len(&self) -> usize {
135 self.table_offsets.values().min().copied().unwrap_or(self.bytecode.len() / 2)
136 }
137}
138
139#[derive(Debug, Clone)]
144pub struct ConstructorMacroBytecode {
145 pub bytecode_res: BytecodeRes,
147 pub has_custom_bootstrap: bool,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154enum ResizeMode {
155 Widen,
157 Shrink,
159}
160
161pub type OffsetMapping = BTreeMap<usize, usize>;
163
164#[derive(Debug, Clone)]
166pub struct ResizedBytecode {
167 pub bytes: BytecodeSegments,
169 pub label_indices: LabelIndices,
171 pub offset_mapping: OffsetMapping,
173}
174
175#[derive(Debug, Clone)]
177pub struct FilledJumps {
178 pub bytes: BytecodeSegments,
180 pub unmatched_jumps: Vec<Jump>,
182 pub label_indices: LabelIndices,
184 pub offset_mapping: OffsetMapping,
186}
187
188fn remap_offset(offset_mapping: &OffsetMapping, offset: usize) -> usize {
190 offset_mapping.get(&offset).copied().unwrap_or(offset)
191}
192
193fn remap_jump_table(jump_table: &JumpTable, offset_mapping: &OffsetMapping) -> JumpTable {
195 jump_table.iter().map(|(&offset, jumps)| (remap_offset(offset_mapping, offset), jumps.clone())).collect()
196}
197
198fn current_offset(original_offsets: &[usize], current_offsets: &[usize], end_offset: usize, original: usize) -> usize {
203 let segment_idx = original_offsets.partition_point(|&offset| offset < original);
204 current_offsets.get(segment_idx).copied().unwrap_or(end_offset)
205}
206
207impl Codegen {
208 pub fn new() -> Self {
210 Self { ast: None, artifact: None, main_bytecode: None, constructor_bytecode: None }
211 }
212
213 pub fn generate_main_bytecode(
222 evm_version: &EVMVersion,
223 contract: &Contract,
224 alternative_main: Option<String>,
225 relax_jumps: bool,
226 ) -> Result<String, CodegenError> {
227 let out = Self::generate_main_bytecode_with_sourcemap(evm_version, contract, alternative_main, relax_jumps)?;
228 Ok(out.bytecode)
229 }
230
231 pub fn generate_main_bytecode_with_sourcemap(
238 evm_version: &EVMVersion,
239 contract: &Contract,
240 alternative_main: Option<String>,
241 relax_jumps: bool,
242 ) -> Result<MainBytecodeOutput, CodegenError> {
243 let mut iter_contract = contract.clone();
247 let initial = (iter_contract.runtime_size.unwrap_or(0), None);
248 converge_codesizes(initial, |(runtime_size, hints)| {
249 iter_contract.runtime_size = Some(*runtime_size);
250 let (output, sizes) =
251 Self::generate_main_bytecode_inner(evm_version, &iter_contract, alternative_main.as_deref(), relax_jumps, hints.as_ref())?;
252 let runtime_size = output.bytecode.len() / 2;
253 Ok((output, (runtime_size, sizes)))
254 })
255 }
256
257 fn generate_main_bytecode_inner(
260 evm_version: &EVMVersion,
261 contract: &Contract,
262 alternative_main: Option<&str>,
263 relax_jumps: bool,
264 codesize_hints: Option<&BTreeMap<String, usize>>,
265 ) -> Result<(MainBytecodeOutput, Option<BTreeMap<String, usize>>), CodegenError> {
266 let contract = Self::update_table_size(evm_version, contract)?;
268
269 let m_macro = Codegen::get_macro_by_name(alternative_main.unwrap_or("MAIN"), &contract)?;
271
272 let mut scope_mgr = ScopeManager::new();
274 scope_mgr.codesize_hints = codesize_hints.cloned().unwrap_or_default();
275 scope_mgr.push_macro(m_macro, 0);
276
277 let bytecode_res: BytecodeRes = Codegen::macro_to_bytecode(evm_version, m_macro, &contract, &mut scope_mgr, 0, relax_jumps)?;
279 Codegen::reject_args_start(&bytecode_res)?;
280 let codesizes = scope_mgr.codesize_hints_used.then_some(scope_mgr.codesizes);
281
282 tracing::debug!(target: "codegen", "Generated main bytecode. Appending table bytecode...");
283
284 let table_out = Codegen::gen_table_bytecode(&contract, bytecode_res, 0, &HashMap::new())?;
286 let bytecode = format!("{}{}", table_out.body, table_out.tables);
287 Ok((MainBytecodeOutput { bytecode, source_map: table_out.source_map, table_offsets: table_out.local_table_offsets }, codesizes))
288 }
289
290 pub fn reject_args_start(res: &BytecodeRes) -> Result<(), CodegenError> {
293 match args_start_span(res) {
294 Some(span) => Err(CodegenError { kind: CodegenErrorKind::ArgsStartOutsideConstructor, span: span.clone_box(), token: None }),
295 None => Ok(()),
296 }
297 }
298
299 pub fn generate_constructor_bytecode(
308 evm_version: &EVMVersion,
309 contract: &Contract,
310 alternative_constructor: Option<String>,
311 relax_jumps: bool,
312 ) -> Result<(String, bool), CodegenError> {
313 let (ctor, contract) = Self::generate_constructor_macro_bytecode(evm_version, contract, alternative_constructor, relax_jumps)?;
314 let has_custom_bootstrap = ctor.has_custom_bootstrap;
315 let out = Codegen::gen_table_bytecode(&contract, ctor.bytecode_res, 0, &HashMap::new())?;
316 Ok((format!("{}{}", out.body, out.tables), has_custom_bootstrap))
317 }
318
319 pub fn generate_constructor_macro_bytecode(
326 evm_version: &EVMVersion,
327 contract: &Contract,
328 alternative_constructor: Option<String>,
329 relax_jumps: bool,
330 ) -> Result<(ConstructorMacroBytecode, Contract), CodegenError> {
331 let contract = Self::update_table_size(evm_version, contract)?;
333
334 let constructor_macro = alternative_constructor.unwrap_or_else(|| String::from("CONSTRUCTOR"));
336
337 let c_macro = Codegen::get_macro_by_name(&constructor_macro, &contract)?;
339
340 let bytecode_res = converge_codesizes(None, |hints: &Option<BTreeMap<String, usize>>| {
343 let mut scope_mgr = ScopeManager::new();
344 scope_mgr.codesize_hints = hints.clone().unwrap_or_default();
345 scope_mgr.push_macro(c_macro, 0);
346 let res = Codegen::macro_to_bytecode(evm_version, c_macro, &contract, &mut scope_mgr, 0, relax_jumps)?;
347 Ok((res, scope_mgr.codesize_hints_used.then_some(scope_mgr.codesizes)))
348 })?;
349
350 let has_custom_bootstrap = bytecode_res.bytes.iter().any(|seg| seg.bytes.as_str() == "f3");
353
354 tracing::info!(target: "codegen", "Constructor is self-generating: {}", has_custom_bootstrap);
355
356 Ok((ConstructorMacroBytecode { bytecode_res, has_custom_bootstrap }, contract))
357 }
358
359 pub fn update_table_size(_evm_version: &EVMVersion, contract: &Contract) -> Result<Contract, CodegenError> {
361 let mut updated = vec![];
363 for table_definition in contract.tables.iter() {
364 if table_definition.size.is_some() {
365 updated.push(table_definition.clone());
366 continue;
367 }
368 let byte_code = Codegen::gen_table_bytecode_builtin(contract, table_definition)?;
370 let size = str_to_bytes32(format!("{:02x}", byte_code.len() / 2).as_str());
371 let mut table = table_definition.clone();
372 table.size = Some(size);
373 updated.push(table);
374 }
375 let mut contract = contract.clone();
376 contract.tables = updated;
377 Ok(contract)
378 }
379
380 pub(crate) fn get_macro_by_name<'a>(name: &str, contract: &'a Contract) -> Result<&'a MacroDefinition, CodegenError> {
382 if let Some(m) = contract.find_macro_by_name(name) {
383 Ok(m)
384 } else {
385 tracing::error!(target: "codegen", "MISSING \"{}\" MACRO!", name);
386 Err(CodegenError {
387 kind: CodegenErrorKind::MissingMacroDefinition(name.to_string()),
388 span: AstSpan(vec![Span { start: 0, end: 0, file: None }]).boxed(),
389 token: None,
390 })
391 }
392 }
393
394 pub fn gen_constant_value(contract: &Contract, name: &str, span: &AstSpan) -> Result<Option<PushValue>, CodegenError> {
396 constant_gen(name, contract, span)
397 }
398
399 pub fn gen_builtin_bytecode(contract: &Contract, bf: &BuiltinFunctionCall, span: AstSpan) -> Result<PushValue, CodegenError> {
402 match bf.kind {
403 BuiltinFunctionKind::FunctionSignature => eval_function_signature(contract, bf),
404 BuiltinFunctionKind::Bytes => eval_builtin_bytes(bf),
405 BuiltinFunctionKind::EventHash => eval_event_hash(contract, bf),
406 BuiltinFunctionKind::LeftPad => builtin_pad(contract, bf, PadDirection::Left),
407 BuiltinFunctionKind::RightPad => builtin_pad(contract, bf, PadDirection::Right),
408 _ if is_runtime_codesize(bf) => {
409 let size = contract.runtime_size.ok_or_else(|| CodegenError {
410 kind: CodegenErrorKind::RuntimeSizeNotComputed,
411 span: span.clone_box(),
412 token: None,
413 })?;
414 Ok(PushValue::from(U256::from(size).to_be_bytes::<32>()))
415 }
416 _ => Err(CodegenError {
417 kind: CodegenErrorKind::UnsupportedBuiltinFunction(format!("{}", bf.kind)),
418 span: span.boxed(),
419 token: None,
420 }),
421 }
422 }
423
424 pub fn gen_table_bytecode_builtin(contract: &Contract, table_definition: &TableDefinition) -> Result<String, CodegenError> {
426 let mut byte_code = String::new();
427 for statement in table_definition.statements.clone() {
428 match &statement.ty {
429 StatementType::Code(code) => {
430 byte_code = format!("{byte_code}{code}");
431 }
432 StatementType::BuiltinFunctionCall(bf) => {
433 if is_runtime_codesize(bf) {
434 return Err(CodegenError {
435 kind: CodegenErrorKind::CodesizeRuntimeInCodeTable,
436 span: statement.span.clone_box(),
437 token: None,
438 });
439 }
440 let push_value = Codegen::gen_builtin_bytecode(contract, bf, statement.span.clone())?;
441 let hex_data = match bf.kind {
443 BuiltinFunctionKind::LeftPad | BuiltinFunctionKind::RightPad => push_value.to_hex_full(),
444 _ => push_value.to_hex_trimmed(),
445 };
446 byte_code = format!("{byte_code}{hex_data}")
447 }
448 StatementType::Constant(name) => {
449 let constant = lookup_constant(name, contract, &statement.span)?;
450 let bytes = match &constant.value {
451 ConstVal::Bytes(bytes) => bytes.clone(),
452 ConstVal::Expression(expr) => {
453 let literal = contract.evaluate_constant_expression(expr)?;
454 Bytes::Raw(bytes_util::bytes32_to_hex_string(&literal, false))
455 }
456 ConstVal::BuiltinFunctionCall(bf) => {
457 let push_value = Codegen::gen_builtin_bytecode(contract, bf, statement.span.clone())?;
458 let hex_data = match bf.kind {
460 BuiltinFunctionKind::LeftPad | BuiltinFunctionKind::RightPad => push_value.to_hex_full(),
461 _ => push_value.to_hex_trimmed(),
462 };
463 Bytes::Raw(hex_data)
464 }
465 _ => {
466 return Err(CodegenError {
467 kind: CodegenErrorKind::InvalidArguments("Constant must be a hex literal or expression".to_string()),
468 span: statement.span.clone_box(),
469 token: None,
470 });
471 }
472 };
473 byte_code = format!("{byte_code}{}", bytes.as_str());
474 }
475 _ => {
476 return Err(CodegenError {
477 kind: CodegenErrorKind::UnsupportedStatementType(format!("{}", statement.ty)),
478 span: table_definition.span.clone_box(),
479 token: None,
480 });
481 }
482 }
483 }
484 Ok(byte_code)
485 }
486
487 pub fn gen_table_bytecode(
496 contract: &Contract,
497 res: BytecodeRes,
498 base_offset: usize,
499 external_table_offsets: &HashMap<String, usize>,
500 ) -> Result<TableBytecodeOutput, CodegenError> {
501 if !res.unmatched_jumps.is_empty() {
502 let labels = res.unmatched_jumps.iter().map(|uj| uj.label.to_string()).collect::<Vec<String>>();
503 tracing::error!(
504 target: "codegen",
505 "Source contains unmatched jump labels \"{}\"",
506 labels.join(", ")
507 );
508
509 return Err(CodegenError {
510 kind: CodegenErrorKind::UnmatchedJumpLabels(labels),
511 span: AstSpan(res.unmatched_jumps.iter().flat_map(|uj| uj.span.0.clone()).collect::<Vec<Span>>()).boxed(),
512 token: None,
513 });
514 }
515
516 tracing::info!(target: "codegen", "GENERATING JUMPTABLE BYTECODE");
517 tracing::debug!(target: "codegen", "Contract has {} source files", contract.source_files.len());
518 tracing::debug!(target: "codegen", "Source map entries: {:?}", contract.source_map);
519 tracing::debug!(target: "codegen", "res.bytes.len() = {}", res.bytes.len());
520 tracing::debug!(target: "codegen", "res.spans.len() = {}", res.spans.len());
521
522 let mut source_map = Vec::new();
526 let mut program_counter = 0; for (instruction_counter, seg) in res.bytes.iter().enumerate() {
529 let instruction_bytecode_length = seg.bytes.len(); let (source_start, source_end, file_id) = if let Some(Some((start, end))) = res.spans.get(instruction_counter) {
534 let (file_id, original_start, original_end) = contract.map_flattened_position_to_source(*start, *end);
536
537 tracing::debug!(target: "codegen", "Source map entry: IC={}, flattened_pos={}..{}, mapped_pos={}..{}, file={}, bytes={}, PC={}, bytecode_len={}, source_len={}",
538 instruction_counter, start, end, original_start, original_end, file_id, seg.bytes.as_str(), program_counter, instruction_bytecode_length, original_end - original_start);
539 (original_start, original_end, file_id)
540 } else {
541 tracing::debug!(target: "codegen", "Source map entry: IC={}, no span, using (0,0,0)", instruction_counter);
543 (0, 0, 0)
544 };
545
546 source_map.push(SourceMapEntry {
547 ic: instruction_counter, pc: program_counter, bytecode_length: instruction_bytecode_length, source_start,
551 source_length: source_end - source_start, file_id,
553 });
554
555 program_counter += instruction_bytecode_length; }
557
558 let mut body = res.bytes.into_iter().map(|seg| seg.bytes.as_str().to_string()).collect::<String>();
559 let body_len = body.len() / 2;
560 let mut tables_bytecode = String::new();
561 let mut local_table_offsets: HashMap<String, usize> = HashMap::new();
562 let mut table_offset = body_len + base_offset;
563
564 res.utilized_tables.iter().try_for_each(|jt| {
565 if res.embedded_tables.contains_key(&jt.name) {
567 tracing::debug!(target: "codegen", "Skipping embedded table \"{}\" from end-placement", jt.name);
568 return Ok(());
569 }
570 if external_table_offsets.contains_key(&jt.name) {
572 tracing::debug!(target: "codegen", "Resolving table \"{}\" via external offset map", jt.name);
573 return Ok(());
574 }
575
576 local_table_offsets.insert(jt.name.to_string(), table_offset);
577
578 let Some(table_size) = &jt.size else {
579 return Err(CodegenError {
580 kind: CodegenErrorKind::MissingTableSize(jt.name.clone()),
581 span: jt.span.clone_box(),
582 token: None,
583 });
584 };
585
586 let size = match bytes_util::hex_to_usize(bytes_util::bytes32_to_hex_string(table_size, false).as_str()) {
587 Ok(s) => s,
588 Err(e) => {
589 tracing::error!(target: "codegen", "Errored converting bytes32 to str. Bytes {:?} with error: {:?}", jt.size, e);
590 return Err(CodegenError {
591 kind: CodegenErrorKind::UsizeConversion(format!("{:?}", jt.size)),
592 span: jt.span.clone_box(),
593 token: None,
594 });
595 }
596 };
597 table_offset += size;
598
599 if jt.kind == TableKind::CodeTable {
600 let table_code = Codegen::gen_table_bytecode_builtin(contract, jt)?;
601
602 tables_bytecode.push_str(&table_code);
603 return Ok(());
604 }
605
606 tracing::info!(target: "codegen", "GENERATING BYTECODE FOR TABLE: \"{}\"", jt.name);
608
609 let is_packed = jt.kind == TableKind::JumpTablePacked;
611 let mut table_code = String::new();
612 jt.statements.iter().try_for_each(|s| {
613 match &s.ty {
614 StatementType::LabelCall(label) => {
615 let offset = match res.label_indices.get_any(label) {
618 Some(l) => l,
619 None => {
620 tracing::error!(
621 target: "codegen",
622 "Definition not found for Jump Table Label: \"{}\"",
623 label
624 );
625 return Err(CodegenError {
626 kind: CodegenErrorKind::UnmatchedJumpLabels(vec![label.clone()]),
627 span: s.span.clone_box(),
628 token: None,
629 });
630 }
631 };
632 if is_packed && offset > 0xFFFF {
633 return Err(CodegenError {
634 kind: CodegenErrorKind::OffsetExceedsTwoBytes(
635 format!("label \"{label}\" in packed jump table \"{}\"", jt.name),
636 offset,
637 ),
638 span: s.span.clone_box(),
639 token: None,
640 });
641 }
642 let hex = format_even_bytes(format!("{offset:02x}"));
643
644 table_code = format!("{table_code}{}", pad_n_bytes(hex.as_str(), if is_packed { 0x02 } else { 0x20 },));
645 }
646 _ => {
647 return Err(CodegenError { kind: CodegenErrorKind::InvalidMacroStatement, span: jt.span.clone_box(), token: None });
648 }
649 }
650 Ok(())
651 })?;
652 tracing::info!(target: "codegen", "SUCCESSFULLY GENERATED BYTECODE FOR TABLE: \"{}\"", jt.name);
653 tables_bytecode.push_str(&table_code);
654 Ok(())
655 })?;
656
657 for jump in res.table_instances.iter() {
658 let offset_opt = local_table_offsets
660 .get(&jump.label)
661 .or_else(|| external_table_offsets.get(&jump.label))
662 .or_else(|| res.embedded_tables.get(&jump.label));
663
664 if let Some(&o) = offset_opt {
665 if o > 0xFFFF {
667 return Err(CodegenError {
668 kind: CodegenErrorKind::OffsetExceedsTwoBytes(format!("table \"{}\" referenced by __tablestart", jump.label), o),
669 span: jump.span.clone_box(),
670 token: None,
671 });
672 }
673 let before = &body[0..jump.bytecode_index * 2 + 2];
674 let after = &body[jump.bytecode_index * 2 + 6..];
675
676 body = format!("{before}{}{after}", pad_n_bytes(format!("{o:02x}").as_str(), 2));
677 tracing::info!(target: "codegen", "FILLED JUMPDEST FOR LABEL \"{}\"", jump.label);
678 } else {
679 tracing::error!(
680 target: "codegen",
681 "Jump table offset not present for jump label \"{}\"",
682 jump.label
683 );
684 }
685 }
686
687 Ok(TableBytecodeOutput { body, tables: tables_bytecode, local_table_offsets, source_map })
688 }
689
690 #[allow(clippy::too_many_arguments)]
707 pub fn macro_to_bytecode<'a>(
708 evm_version: &EVMVersion,
709 macro_def: &'a MacroDefinition,
710 contract: &'a Contract,
711 scope_mgr: &mut ScopeManager<'a>,
712 mut offset: usize,
713 relax_jumps: bool,
714 ) -> Result<BytecodeRes, CodegenError> {
715 if scope_mgr.depth() > MAX_MACRO_RECURSION_DEPTH {
717 let macro_chain = scope_mgr.macro_stack().iter().map(|m| m.name.as_str()).collect::<Vec<_>>().join(" -> ");
718 return Err(CodegenError {
719 kind: CodegenErrorKind::InvalidMacroArgumentType(format!(
720 "Maximum macro recursion depth ({}) exceeded while compiling macro '{}'.\nMacro call chain: {}\nThis likely indicates circular macro calls or infinitely recursive macro expansion.",
721 MAX_MACRO_RECURSION_DEPTH, macro_def.name, macro_chain
722 )),
723 span: macro_def.span.clone_box(),
724 token: None,
725 });
726 }
727
728 let mut bytes = BytecodeSegments::new();
730 let mut spans: Vec<Option<(usize, usize)>> = Vec::default();
731 let ir_bytes = macro_def.to_irbytecode(evm_version)?.0;
732
733 let mut jump_table = JumpTable::new();
735 let mut label_indices = LabelIndices::new();
736 let mut table_instances = Jumps::new();
737 let mut utilized_tables: Vec<TableDefinition> = Vec::new();
738 let mut embedded_tables = std::collections::BTreeMap::new();
739 let start_offset = offset;
740
741 for ir_byte in ir_bytes.iter() {
743 let starting_offset = offset;
744
745 let span_info = if !ir_byte.span.0.is_empty() {
748 ir_byte.span.0.first().and_then(|s| {
749 if s.start != 0 || s.end != 0 {
750 contract.map_original_span_to_flattened(s)
752 } else {
753 None
754 }
755 })
756 } else {
757 None
758 };
759
760 match &ir_byte.ty {
761 IRByteType::Bytes(b) => {
762 offset += b.len(); bytes.push_with_offset(starting_offset, b.to_owned());
764 spans.push(span_info);
765 }
766 IRByteType::Constant(name) => {
767 if let Some(push_value) = constant_gen(name, contract, ir_byte.span)? {
768 let push_bytes = push_value.to_hex_with_opcode(evm_version);
769 offset += push_bytes.len() / 2;
770 tracing::debug!(target: "codegen", "OFFSET: {}, PUSH BYTES: {:?}", offset, push_bytes);
771 bytes.push_with_offset(starting_offset, Bytes::Raw(push_bytes));
772 spans.push(span_info);
773 } }
775 IRByteType::Statement(s) => {
776 let (push_bytes, mut push_spans) = statement_gen(
777 evm_version,
778 s,
779 contract,
780 macro_def,
781 scope_mgr,
782 &mut offset,
783 &mut jump_table,
784 &mut label_indices,
785 &mut table_instances,
786 &mut utilized_tables,
787 &mut embedded_tables,
788 starting_offset,
789 relax_jumps,
790 )?;
791 spans.append(&mut push_spans);
794 bytes.extend(push_bytes);
795 }
796 IRByteType::ArgCall(parent_macro_name, arg_name) => {
797 let bytes_before = bytes.len();
798 bubble_arg_call(
801 evm_version,
802 parent_macro_name.to_owned(),
803 arg_name.to_owned(),
804 &mut bytes,
805 macro_def,
806 contract,
807 scope_mgr,
808 &mut offset,
809 &mut jump_table,
810 &mut table_instances,
811 &mut utilized_tables,
812 ir_byte.span,
813 relax_jumps,
814 )?;
815 let bytes_added = bytes.len() - bytes_before;
817 for _ in 0..bytes_added {
818 spans.push(span_info);
819 }
820 }
821 }
822 }
823
824 let is_top_level = scope_mgr.depth() == 1;
831 if is_top_level {
832 let bytes_before = bytes.len();
833 let has_appended_code = contract.macros.values().any(|m| m.outlined) || !utilized_tables.is_empty();
836 if macro_def.test && has_appended_code {
837 bytes.push_with_offset(offset, Bytes::Raw(Opcode::Stop.to_string()));
838 offset += 1;
839 }
840 bytes = Codegen::append_functions(
841 evm_version,
842 contract,
843 scope_mgr,
844 &mut offset,
845 &mut jump_table,
846 &mut label_indices,
847 &mut table_instances,
848 bytes,
849 )?;
850 let bytes_added = bytes.len() - bytes_before;
852 for _ in 0..bytes_added {
853 spans.push(None);
854 }
855 } else {
856 scope_mgr.pop_macro();
859 }
860
861 bytes.measure_circular_codesize(¯o_def.name, start_offset, start_offset + bytes.total_byte_size());
863
864 let segment_count = bytes.len();
868 let FilledJumps { mut bytes, unmatched_jumps, label_indices, offset_mapping } = if is_top_level {
869 Codegen::fill_unmatched(bytes, &jump_table, &label_indices, relax_jumps)?
870 } else {
871 let widened = Codegen::widen_jump_offsets(bytes, &JumpTable::new(), &label_indices);
872 let unmatched_jumps = remap_jump_table(&jump_table, &widened.offset_mapping)
873 .into_iter()
874 .flat_map(|(offset, jumps)| jumps.into_iter().map(move |jump| Jump { bytecode_index: offset, ..jump }))
875 .collect();
876 FilledJumps {
877 bytes: widened.bytes,
878 unmatched_jumps,
879 label_indices: widened.label_indices,
880 offset_mapping: widened.offset_mapping,
881 }
882 };
883 for table_instance in table_instances.iter_mut() {
885 table_instance.bytecode_index = remap_offset(&offset_mapping, table_instance.bytecode_index);
886 }
887
888 if bytes.len() != segment_count {
890 while spans.len() < bytes.len() {
893 spans.push(None);
894 }
895 }
896
897 let growth = bytes.resolve_circular_codesize();
900 debug_assert_eq!(growth, 0, "circular codesize placeholders must be widened before jump resolution");
901 scope_mgr.codesizes.insert(macro_def.name.clone(), bytes.total_byte_size());
902
903 tracing::info!(target: "codegen", "BytecodeRes created with {} bytes and {} spans", bytes.len(), spans.len());
904 Ok(BytecodeRes { bytes, spans, label_indices, unmatched_jumps, table_instances, utilized_tables, embedded_tables })
905 }
906
907 pub fn relax_jump_offsets(bytes: BytecodeSegments, jump_table: &JumpTable, label_indices: &LabelIndices) -> ResizedBytecode {
920 Self::resize_span_dependent_pushes(bytes, jump_table, label_indices, ResizeMode::Shrink)
921 }
922
923 pub fn widen_jump_offsets(bytes: BytecodeSegments, jump_table: &JumpTable, label_indices: &LabelIndices) -> ResizedBytecode {
933 Self::resize_span_dependent_pushes(bytes, jump_table, label_indices, ResizeMode::Widen)
934 }
935
936 fn resize_span_dependent_pushes(
941 mut bytes: BytecodeSegments,
942 jump_table: &JumpTable,
943 label_indices: &LabelIndices,
944 mode: ResizeMode,
945 ) -> ResizedBytecode {
946 let original_offsets: Vec<usize> = bytes.iter().map(|segment| segment.offset).collect();
949 let base_offset = original_offsets.first().copied().unwrap_or(0);
950 let mut current_label_indices = label_indices.clone();
951 let mut cumulative_offset_mapping: OffsetMapping = original_offsets.iter().map(|&o| (o, o)).collect();
952 let max_iterations = match mode {
955 ResizeMode::Shrink => JUMP_RELAXATION_MAX_ITERATIONS,
956 ResizeMode::Widen => bytes.len() + 1,
957 };
958
959 let mut iteration = 0;
960 let mut changed = true;
961 while changed && iteration < max_iterations {
962 changed = false;
963 iteration += 1;
964
965 let offsets: Vec<usize> = bytes.calculate_offsets().into_iter().map(|offset| base_offset + offset).collect();
966 let total_length = bytes.total_byte_size();
967 let end_offset = base_offset + total_length;
968
969 for (segment_idx, segment) in bytes.iter_mut().enumerate() {
970 match segment.bytes {
971 Bytes::JumpPlaceholder(ref mut placeholder) => {
972 let Some(jump) = jump_table.get(&original_offsets[segment_idx]).and_then(|jumps| jumps.first()) else {
973 continue;
974 };
975 let Ok(Some(target_offset)) = current_label_indices.get(&placeholder.label, &jump.scope_id) else {
977 continue;
978 };
979 let new_opcode = match mode {
980 ResizeMode::Widen if !placeholder.push_opcode.can_represent(target_offset) => {
981 Some(PushOpcode::optimal_for(target_offset))
982 }
983 ResizeMode::Shrink
985 if placeholder.push_opcode == PushOpcode::Push2
986 && (target_offset <= 0xFF || (target_offset == 0x100 && target_offset > offsets[segment_idx])) =>
987 {
988 Some(PushOpcode::Push1)
989 }
990 _ => None,
991 };
992 if let Some(new_opcode) = new_opcode {
993 tracing::debug!(
994 target: "codegen",
995 "Resizing jump to '{}' at offset {} from {:?} to {:?} (target={})",
996 placeholder.label,
997 offsets[segment_idx],
998 placeholder.push_opcode,
999 new_opcode,
1000 target_offset
1001 );
1002 placeholder.push_opcode = new_opcode;
1003 changed = true;
1004 }
1005 }
1006 Bytes::CircularCodesizePlaceholder(ref mut placeholder) if mode == ResizeMode::Widen => {
1007 let size = placeholder.size(
1008 |(start, end)| {
1009 current_offset(&original_offsets, &offsets, end_offset, end)
1010 - current_offset(&original_offsets, &offsets, end_offset, start)
1011 },
1012 total_length,
1013 );
1014 if !placeholder.push_opcode.can_represent(size) {
1015 placeholder.push_opcode = PushOpcode::optimal_for(size);
1016 changed = true;
1017 }
1018 }
1019 _ => {}
1020 }
1021 }
1022
1023 if changed {
1024 let new_offsets: Vec<usize> = bytes.calculate_offsets().into_iter().map(|offset| base_offset + offset).collect();
1026 let offset_mapping: OffsetMapping =
1027 bytes.iter().zip(&new_offsets).map(|(segment, &new_offset)| (segment.offset, new_offset)).collect();
1028
1029 for final_offset in cumulative_offset_mapping.values_mut() {
1030 *final_offset = remap_offset(&offset_mapping, *final_offset);
1031 }
1032 current_label_indices.update_offsets(&offset_mapping);
1033 for (segment, new_offset) in bytes.iter_mut().zip(new_offsets) {
1034 segment.offset = new_offset;
1035 }
1036 }
1037 }
1038
1039 if changed {
1040 tracing::warn!(target: "codegen", "{:?} of span-dependent pushes hit max iterations ({})", mode, max_iterations);
1041 } else {
1042 tracing::info!(target: "codegen", "{:?} of span-dependent pushes converged after {} iterations", mode, iteration);
1043 }
1044
1045 let final_offsets: Vec<usize> = bytes.iter().map(|segment| segment.offset).collect();
1047 let final_end_offset = base_offset + bytes.total_byte_size();
1048 for segment in bytes.iter_mut() {
1049 if let Bytes::CircularCodesizePlaceholder(ref mut placeholder) = segment.bytes
1050 && let Some((start, end)) = placeholder.measured_range
1051 {
1052 placeholder.measured_range = Some((
1053 current_offset(&original_offsets, &final_offsets, final_end_offset, start),
1054 current_offset(&original_offsets, &final_offsets, final_end_offset, end),
1055 ));
1056 }
1057 }
1058
1059 ResizedBytecode { bytes, label_indices: current_label_indices, offset_mapping: cumulative_offset_mapping }
1060 }
1061
1062 pub fn fill_unmatched(
1082 bytes: BytecodeSegments,
1083 jump_table: &JumpTable,
1084 label_indices: &LabelIndices,
1085 relax_jumps: bool,
1086 ) -> Result<FilledJumps, CodegenError> {
1087 let ResizedBytecode { mut bytes, mut label_indices, mut offset_mapping } =
1090 Self::widen_jump_offsets(bytes, jump_table, label_indices);
1091 if relax_jumps {
1092 let relaxed = Self::relax_jump_offsets(bytes, &remap_jump_table(jump_table, &offset_mapping), &label_indices);
1093 bytes = relaxed.bytes;
1094 label_indices = relaxed.label_indices;
1095 for final_offset in offset_mapping.values_mut() {
1096 *final_offset = remap_offset(&relaxed.offset_mapping, *final_offset);
1097 }
1098 }
1099
1100 let resized_jump_table = remap_jump_table(jump_table, &offset_mapping);
1102 let unmatched_jumps = bytes.resolve_jumps(&resized_jump_table, &label_indices).map_err(|label_error| {
1103 match label_error {
1105 LabelError::DuplicateLabelAcrossSiblings(label_name) => {
1106 let span = jump_table
1108 .values()
1109 .flat_map(|jumps| jumps.iter())
1110 .find(|jump| jump.label == label_name)
1111 .map(|jump| jump.span.clone_box())
1112 .unwrap_or_else(|| Box::new(AstSpan::default()));
1113
1114 CodegenError { kind: CodegenErrorKind::DuplicateLabelAcrossSiblings(label_name), span, token: None }
1115 }
1116 LabelError::JumpTargetTooLarge { label, target, opcode } => {
1117 let span = jump_table
1119 .values()
1120 .flat_map(|jumps| jumps.iter())
1121 .find(|jump| jump.label == label)
1122 .map(|jump| jump.span.clone_box())
1123 .unwrap_or_else(|| Box::new(AstSpan::default()));
1124
1125 CodegenError { kind: CodegenErrorKind::JumpTargetTooLarge { label, target, opcode }, span, token: None }
1126 }
1127 LabelError::DuplicateLabelInScope(label_name) => {
1128 let span = jump_table
1130 .values()
1131 .flat_map(|jumps| jumps.iter())
1132 .find(|jump| jump.label == label_name)
1133 .map(|jump| jump.span.clone_box())
1134 .unwrap_or_else(|| Box::new(AstSpan::default()));
1135
1136 CodegenError { kind: CodegenErrorKind::DuplicateLabelInScope(label_name), span, token: None }
1137 }
1138 }
1139 })?;
1140
1141 for jump in &unmatched_jumps {
1143 tracing::warn!(
1144 target: "codegen",
1145 "UNMATCHED JUMP LABEL \"{}\" AT BYTECODE INDEX {} (scope_id={:?})",
1146 jump.label, jump.bytecode_index, jump.scope_id
1147 );
1148 }
1149
1150 let offsets = bytes.calculate_offsets();
1154 for (idx, segment) in bytes.iter().enumerate() {
1155 if let Bytes::AssertPcPlaceholder(data) = &segment.bytes {
1156 let actual_offset = offsets[idx];
1158
1159 if actual_offset != data.expected_offset {
1160 tracing::error!(
1161 target: "codegen",
1162 "PC assertion failed: expected 0x{:x}, got 0x{:x}",
1163 data.expected_offset,
1164 actual_offset
1165 );
1166 return Err(CodegenError {
1167 kind: CodegenErrorKind::AssertPcFailed(data.expected_offset, actual_offset),
1168 span: data.span.clone_box(),
1169 token: None,
1170 });
1171 }
1172
1173 tracing::debug!(
1174 target: "codegen",
1175 "PC assertion passed: position is 0x{:x} as expected (after relaxation)",
1176 actual_offset
1177 );
1178 }
1179 }
1180
1181 let mut filtered_bytes = BytecodeSegments::new();
1183 for segment in bytes.iter() {
1184 if !matches!(segment.bytes, Bytes::AssertPcPlaceholder(_)) {
1185 filtered_bytes.push(segment.clone());
1186 }
1187 }
1188
1189 Ok(FilledJumps { bytes: filtered_bytes, unmatched_jumps, label_indices, offset_mapping })
1190 }
1191
1192 #[allow(clippy::too_many_arguments)]
1202 pub fn append_functions<'a>(
1203 evm_version: &EVMVersion,
1204 contract: &'a Contract,
1205 scope_mgr: &mut ScopeManager<'a>,
1206 offset: &mut usize,
1207 jump_table: &mut JumpTable,
1208 label_indices: &mut LabelIndices,
1209 table_instances: &mut Jumps,
1210 mut bytes: BytecodeSegments,
1211 ) -> Result<BytecodeSegments, CodegenError> {
1212 let functions: Vec<_> = contract.macros.values().filter(|m| m.outlined).collect();
1215 let mut current_offset = *offset;
1216 let goto_scope_id = huff_neo_utils::scope::ScopeId::new(vec![], 0);
1217
1218 let mut function_offsets: Vec<(&MacroDefinition, usize)> = Vec::new();
1221
1222 for macro_def in &functions {
1223 if let Err(err_msg) = label_indices.insert(format!("goto_{}", macro_def.name.clone()), current_offset, &goto_scope_id) {
1225 tracing::error!(target: "codegen", "INTERNAL ERROR: Duplicate goto_ label for function '{}': {}", macro_def.name, err_msg);
1227 return Err(CodegenError {
1228 kind: CodegenErrorKind::DuplicateLabelInScope(format!("goto_{}", macro_def.name)),
1229 span: macro_def.span.clone_box(),
1230 token: None,
1231 });
1232 }
1233
1234 function_offsets.push((macro_def, current_offset));
1235
1236 scope_mgr.push_macro(macro_def, current_offset);
1241 let temp_res = Codegen::macro_to_bytecode(evm_version, macro_def, contract, scope_mgr, current_offset + 1, false)?;
1242 let macro_code_len = temp_res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>();
1243 let stack_swaps_len = macro_def.returns;
1244 current_offset += macro_code_len + stack_swaps_len + 2; }
1247
1248 for (macro_def, func_offset) in function_offsets {
1251 scope_mgr.push_macro(macro_def, func_offset);
1253
1254 let mut res = Codegen::macro_to_bytecode(evm_version, macro_def, contract, scope_mgr, func_offset + 1, false)?;
1256
1257 for j in res.unmatched_jumps.iter_mut() {
1258 let new_index = j.bytecode_index;
1259 j.bytecode_index = 0;
1260 let mut new_jumps = if let Some(jumps) = jump_table.get(&new_index) { jumps.clone() } else { vec![] };
1261 new_jumps.push(j.clone());
1262 jump_table.insert(new_index, new_jumps);
1263 }
1264 table_instances.extend(res.table_instances);
1265 label_indices.extend(res.label_indices);
1266
1267 let macro_code_len = res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>();
1268
1269 let stack_swaps = (0..macro_def.returns).map(|i| format!("{:02x}", 0x90 + i)).collect::<Vec<_>>();
1272
1273 bytes.push_with_offset(func_offset, Bytes::Raw(Opcode::Jumpdest.to_string()));
1275 res.bytes.push_with_offset(func_offset + macro_code_len + 1, Bytes::Raw(format!("{}{}", stack_swaps.join(""), Opcode::Jump)));
1276 bytes.extend(res.bytes);
1277
1278 }
1281
1282 *offset = current_offset;
1283 Ok(bytes)
1284 }
1285
1286 #[allow(clippy::too_many_arguments)]
1294 pub fn churn(
1295 &mut self,
1296 evm_version: &EVMVersion,
1297 file: Arc<FileSource>,
1298 args: Vec<DynSolValue>,
1299 main_bytecode: &str,
1300 constructor_bytecode: &str,
1301 has_custom_bootstrap: bool,
1302 main_source_map: Option<Vec<SourceMapEntry>>,
1303 constructor_source_map: Option<Vec<SourceMapEntry>>,
1304 no_size_limit: bool,
1305 ) -> Result<Artifact, CodegenError> {
1306 let main = MainBytecodeOutput {
1307 bytecode: main_bytecode.to_string(),
1308 source_map: main_source_map.unwrap_or_default(),
1309 table_offsets: HashMap::new(),
1310 };
1311 let constructor = if constructor_bytecode.is_empty() {
1312 None
1313 } else {
1314 let mut bytes = BytecodeSegments::new();
1315 bytes.push_with_offset(0, Bytes::Raw(constructor_bytecode.to_string()));
1316 let bytecode_res = BytecodeRes { bytes, ..Default::default() };
1317 Some(ConstructorMacroBytecode { bytecode_res, has_custom_bootstrap })
1318 };
1319 let contract = Contract::default();
1322 let mut artifact = self.assemble_artifact(evm_version, file, &contract, args, main, constructor, no_size_limit)?;
1323 artifact.constructor_map = constructor_source_map;
1325 Ok(artifact)
1326 }
1327
1328 #[allow(clippy::too_many_arguments)]
1348 pub fn assemble_artifact(
1349 &mut self,
1350 evm_version: &EVMVersion,
1351 file: Arc<FileSource>,
1352 contract: &Contract,
1353 args: Vec<DynSolValue>,
1354 main: MainBytecodeOutput,
1355 constructor: Option<ConstructorMacroBytecode>,
1356 no_size_limit: bool,
1357 ) -> Result<Artifact, CodegenError> {
1358 let artifact: &mut Artifact = if let Some(art) = &mut self.artifact {
1359 art
1360 } else {
1361 self.artifact = Some(Artifact::default());
1362 self.artifact.as_mut().unwrap()
1363 };
1364
1365 let MainBytecodeOutput { bytecode: main_bytecode, source_map: main_source_map, table_offsets: main_table_offsets } = main;
1366
1367 let contract_length = main_bytecode.len() / 2;
1368
1369 if !no_size_limit && contract_length > evm_version.max_code_size() {
1371 return Err(CodegenError {
1372 kind: CodegenErrorKind::ContractSizeLimitExceeded(contract_length, evm_version.max_code_size()),
1373 span: AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }]).boxed(),
1374 token: None,
1375 });
1376 }
1377
1378 let (ctor_body_len, has_custom_bootstrap) = match &constructor {
1382 Some(c) => (c.bytecode_res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>(), c.has_custom_bootstrap),
1384 None => (0, false),
1385 };
1386
1387 let (bootstrap_code, effective_bootstrap_size) =
1390 if has_custom_bootstrap { (String::default(), 0) } else { deploy_bootstrap(contract_length, ctor_body_len) };
1391
1392 let runtime_start_in_deployment = ctor_body_len + effective_bootstrap_size;
1396 let ctor_tables_base_offset = effective_bootstrap_size + contract_length;
1399 let external_table_offsets: HashMap<String, usize> =
1400 main_table_offsets.iter().map(|(name, off)| (name.clone(), runtime_start_in_deployment + off)).collect();
1401
1402 let args_start_span = constructor.as_ref().and_then(|c| args_start_span(&c.bytecode_res)).cloned();
1403 let (mut constructor_body, ctor_only_tables, constructor_source_map) = match constructor {
1404 Some(c) => {
1405 let out = Codegen::gen_table_bytecode(contract, c.bytecode_res, ctor_tables_base_offset, &external_table_offsets)?;
1406 (out.body, out.tables, Some(out.source_map))
1407 }
1408 None => (String::new(), String::new(), None),
1409 };
1410
1411 let args_start = runtime_start_in_deployment + contract_length + ctor_only_tables.len() / 2;
1413 if constructor_body.contains(ARGS_START_PLACEHOLDER) {
1414 if args_start > 0xFFFF {
1415 return Err(CodegenError {
1416 kind: CodegenErrorKind::OffsetExceedsTwoBytes("constructor arguments for __argsstart()".to_string(), args_start),
1417 span: args_start_span.unwrap_or_else(|| AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }])).boxed(),
1418 token: None,
1419 });
1420 }
1421 constructor_body = constructor_body.replace(ARGS_START_PLACEHOLDER, &format!("{}{args_start:04x}", Opcode::Push2));
1422 }
1423
1424 let constructor_args = Codegen::abi_encode_constructor_args(args);
1425 let deployment_bytecode = format!("{constructor_body}{bootstrap_code}{main_bytecode}{ctor_only_tables}{constructor_args}");
1426
1427 let initcode_length = deployment_bytecode.len() / 2;
1429 if !no_size_limit && initcode_length > evm_version.max_initcode_size() {
1430 return Err(CodegenError {
1431 kind: CodegenErrorKind::InitcodeSizeLimitExceeded(initcode_length, evm_version.max_initcode_size()),
1432 span: AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }]).boxed(),
1433 token: None,
1434 });
1435 }
1436
1437 artifact.bytecode = deployment_bytecode.to_lowercase();
1438 artifact.runtime = main_bytecode.to_lowercase();
1439 artifact.has_custom_bootstrap = has_custom_bootstrap;
1440 artifact.file = file;
1441
1442 artifact.runtime_map = Some(main_source_map);
1444 artifact.constructor_map = constructor_source_map;
1445
1446 Ok(artifact.clone())
1447 }
1448
1449 pub fn encode_constructor_args(args: &[String], types: Option<&[String]>) -> Result<Vec<DynSolValue>, CodegenError> {
1453 let invalid =
1454 |msg: String| CodegenError { kind: CodegenErrorKind::InvalidConstructorArgs(msg), span: AstSpan(vec![]).boxed(), token: None };
1455 if args.is_empty() {
1456 return Ok(vec![]);
1457 }
1458 let Some(types) = types else {
1459 return args.iter().map(|arg| EToken::try_from(arg.clone()).map(|token| token.0).map_err(invalid)).collect();
1460 };
1461 if args.len() != types.len() {
1462 return Err(invalid(format!(
1463 "the constructor takes {} argument(s) ({}), but {} were given",
1464 types.len(),
1465 types.join(", "),
1466 args.len()
1467 )));
1468 }
1469 args.iter()
1470 .zip(types)
1471 .map(|(arg, ty)| {
1472 DynSolType::parse(ty).and_then(|ty| ty.coerce_str(arg)).map_err(|e| invalid(format!("\"{arg}\" is not a valid {ty}: {e}")))
1473 })
1474 .collect()
1475 }
1476
1477 pub fn declared_constructor_types(contract: &Contract) -> Option<Vec<String>> {
1479 contract
1480 .functions
1481 .iter()
1482 .find(|function| function.name.eq_ignore_ascii_case("constructor"))
1483 .map(|function| function.inputs.iter().map(|input| input.arg_type.clone().unwrap_or_default()).collect())
1484 }
1485
1486 pub fn abi_encode_constructor_args(args: Vec<DynSolValue>) -> String {
1488 if args.is_empty() {
1489 return String::new();
1490 }
1491 hex::encode(DynSolValue::Tuple(args).abi_encode_params())
1492 }
1493
1494 pub fn export(output: String, art: &Artifact) -> Result<(), CodegenError> {
1502 let serialized_artifact = serde_json::to_string_pretty(art).unwrap();
1503 let file_path = Path::new(&output);
1505 if let Some(p) = file_path.parent()
1506 && let Err(e) = fs::create_dir_all(p)
1507 {
1508 return Err(CodegenError {
1509 kind: CodegenErrorKind::IOError(e.to_string()),
1510 span: AstSpan(vec![Span {
1511 start: 0,
1512 end: 0,
1513 file: Some(Arc::new(FileSource { path: output, source: None, access: None, dependencies: vec![] })),
1514 }])
1515 .boxed(),
1516 token: None,
1517 });
1518 }
1519 if let Err(e) = fs::write(file_path, serialized_artifact) {
1520 return Err(CodegenError {
1521 kind: CodegenErrorKind::IOError(e.to_string()),
1522 span: AstSpan(vec![Span {
1523 start: 0,
1524 end: 0,
1525 file: Some(Arc::new(FileSource { path: output, source: None, access: None, dependencies: vec![] })),
1526 }])
1527 .boxed(),
1528 token: None,
1529 });
1530 }
1531 Ok(())
1532 }
1533
1534 pub fn abi_gen(&mut self, ast: Contract, output: Option<String>) -> Result<Abi, CodegenError> {
1544 let abi: Abi = ast.into();
1545
1546 let art: &Artifact = match &mut self.artifact {
1548 Some(artifact) => {
1549 artifact.abi = Some(abi.clone());
1550 artifact
1551 }
1552 None => {
1553 self.artifact = Some(Artifact { abi: Some(abi.clone()), ..Default::default() });
1554 self.artifact.as_ref().unwrap()
1555 }
1556 };
1557
1558 if let Some(o) = output {
1560 Codegen::export(o, art)?;
1562 }
1563
1564 Ok(abi)
1566 }
1567}