diff --git a/transpiler/codegen/contract.py b/transpiler/codegen/contract.py index bd8edde9..acad694c 100644 --- a/transpiler/codegen/contract.py +++ b/transpiler/codegen/contract.py @@ -402,12 +402,15 @@ def _generate_mapping_mutator( key_name = f'key{key_index}' key_params.append(f'{key_name}: {key_ts_type}') + # Convert non-string keys to string for Record indexing + key_access = key_name if key_ts_type == 'string' else f'String({key_name})' + if current_type.value_type.is_mapping: null_coalesce_lines.append( - f'{body_indent}{access_path}[{key_name}] ??= {{}};' + f'{body_indent}{access_path}[{key_access}] ??= {{}};' ) - access_path = f'{access_path}[{key_name}]' + access_path = f'{access_path}[{key_access}]' current_type = current_type.value_type key_index += 1 diff --git a/transpiler/codegen/statement.py b/transpiler/codegen/statement.py index 77e867b8..cf66b2ff 100644 --- a/transpiler/codegen/statement.py +++ b/transpiler/codegen/statement.py @@ -72,7 +72,7 @@ def __init__( super().__init__(ctx) self._expr = expr_generator self._type_converter = type_converter - self._yul_transpiler = YulTranspiler() + self._yul_transpiler = YulTranspiler(known_constants=ctx.known_constants) # ========================================================================= # MAIN DISPATCH diff --git a/transpiler/codegen/type_converter.py b/transpiler/codegen/type_converter.py index d8bcc12a..02ba96dd 100644 --- a/transpiler/codegen/type_converter.py +++ b/transpiler/codegen/type_converter.py @@ -190,15 +190,22 @@ def generate_type_cast( if expr != 'this' and not expr.startswith('"') and not expr.startswith("'"): return f'{expr}._contractAddress' - # Handle bytes32 literals + # Handle bytes32 literals and expressions if type_name == 'bytes32': if isinstance(inner_expr, Literal) and inner_expr.kind in ('number', 'hex'): return self._to_padded_bytes32(inner_expr.value) + # Non-literal: convert bigint to padded hex string at runtime + expr = generate_expression_fn(inner_expr) + return f'`0x${{{expr}.toString(16).padStart(64, "0")}}`' # Handle bytes types if type_name.startswith('bytes') and type_name != 'bytes': if isinstance(inner_expr, Literal) and inner_expr.kind in ('number', 'hex'): return self._to_padded_bytes32(inner_expr.value) + # Non-literal: convert bigint to padded hex string at runtime + byte_size = int(type_name[5:]) if type_name[5:].isdigit() else 32 + expr = generate_expression_fn(inner_expr) + return f'`0x${{{expr}.toString(16).padStart({byte_size * 2}, "0")}}`' # For numeric types (uint256, int128, etc.), just generate the inner expression # TypeScript's bigint handles the underlying value diff --git a/transpiler/codegen/yul.py b/transpiler/codegen/yul.py index ba8a1d6f..0b93bf84 100644 --- a/transpiler/codegen/yul.py +++ b/transpiler/codegen/yul.py @@ -48,6 +48,14 @@ class YulTranspiler: - mstore/mload → memory operations (usually no-op for simulation) """ + def __init__(self, known_constants: set = None): + """Initialize with optional set of known constant names. + + Args: + known_constants: Set of constant names that should be prefixed with 'Constants.' + """ + self._known_constants = known_constants or set() + def transpile(self, yul_code: str) -> str: """ Transpile a Yul assembly block to TypeScript. @@ -285,6 +293,10 @@ def _transpile_expr(self, expr: str, slot_vars: Dict[str, str]) -> str: if expr.isdigit(): return f'{expr}n' + # Check if identifier is a known constant from type registry + if expr in self._known_constants: + return f'Constants.{expr}' + # Return as-is (identifier) return expr diff --git a/transpiler/sol2ts.py b/transpiler/sol2ts.py index e1d7b64d..4ac08d7a 100644 --- a/transpiler/sol2ts.py +++ b/transpiler/sol2ts.py @@ -13,7 +13,7 @@ - Interface and contract inheritance Usage: - python -m transpiler src/ + python transpiler/sol2ts.py src/ This refactored version uses a modular architecture with separate packages for: - lexer: Tokenization (tokens.py, lexer.py) @@ -244,6 +244,8 @@ def main(): parser.add_argument('input', help='Input Solidity file or directory') parser.add_argument('-o', '--output', default='transpiler/ts-output', help='Output directory') parser.add_argument('--stdout', action='store_true', help='Print to stdout instead of file') + parser.add_argument('-d', '--discover', action='append', metavar='DIR', + help='Directory to scan for type discovery') parser.add_argument('--stub', action='append', metavar='CONTRACT', help='Contract name to generate as minimal stub') parser.add_argument('--emit-metadata', action='store_true', @@ -254,12 +256,15 @@ def main(): args = parser.parse_args() input_path = Path(args.input) - discovery_dirs = [str(input_path)] if input_path.is_dir() else [str(input_path.parent)] + discovery_dirs = args.discover or ([str(input_path)] if input_path.is_dir() else [str(input_path.parent)]) stubbed_contracts = args.stub or [] emit_metadata = args.emit_metadata or args.metadata_only if input_path.is_file(): + # Use first discovery dir as source_dir for correct import path calculation + source_dir = discovery_dirs[0] if discovery_dirs else str(input_path.parent) transpiler = SolidityToTypeScriptTranspiler( + source_dir=source_dir, output_dir=args.output, discovery_dirs=discovery_dirs, stubbed_contracts=stubbed_contracts,