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
7 changes: 5 additions & 2 deletions transpiler/codegen/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion transpiler/codegen/statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion transpiler/codegen/type_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions transpiler/codegen/yul.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
9 changes: 7 additions & 2 deletions transpiler/sol2ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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',
Expand All @@ -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,
Expand Down