diff --git a/crates/modules-interfaces/src/nested.rs b/crates/modules-interfaces/src/nested.rs index 57021cf8..8f7c7f74 100644 --- a/crates/modules-interfaces/src/nested.rs +++ b/crates/modules-interfaces/src/nested.rs @@ -72,8 +72,19 @@ pub enum ExecutorSelector { #[serde(rename_all = "snake_case")] pub enum NestedStorageType { Default, - LatestFinal, - LatestNonFinal, + #[serde(alias = "latest_final")] + LatestFinalized, + #[serde(alias = "latest_non_final")] + LatestDecided, +} + +impl NestedStorageType { + #[deprecated(note = "use `NestedStorageType::LatestFinalized`")] + #[allow(non_upper_case_globals)] + pub const LatestFinal: Self = Self::LatestFinalized; + #[deprecated(note = "use `NestedStorageType::LatestDecided`")] + #[allow(non_upper_case_globals)] + pub const LatestNonFinal: Self = Self::LatestDecided; } /// Permission bits carried across an executor boundary. @@ -231,6 +242,28 @@ pub struct NestedRunReply { mod tests { use super::*; + #[test] + #[allow(deprecated)] + fn nested_storage_names_preserve_wire_values_and_legacy_aliases() { + fn legacy_match(value: NestedStorageType) -> u8 { + match value { + NestedStorageType::Default => 0, + NestedStorageType::LatestFinal => 1, + NestedStorageType::LatestNonFinal => 2, + } + } + + assert_eq!(legacy_match(NestedStorageType::LatestDecided), 2); + assert_eq!( + serde_json::from_str::("\"latest_non_final\"").unwrap(), + NestedStorageType::LatestDecided + ); + assert_eq!( + genlayer_calldata::encode_obj(&NestedStorageType::LatestFinal), + genlayer_calldata::encode_obj(&NestedStorageType::LatestFinalized) + ); + } + #[test] fn unknown_permission_bits_are_denied() { let encoded = diff --git a/docs/website/src/spec/02-execution-environment/03-wasi_genlayer_sdk/02-gl_call.rst b/docs/website/src/spec/02-execution-environment/03-wasi_genlayer_sdk/02-gl_call.rst index 8aafb847..bc2885c8 100644 --- a/docs/website/src/spec/02-execution-environment/03-wasi_genlayer_sdk/02-gl_call.rst +++ b/docs/website/src/spec/02-execution-environment/03-wasi_genlayer_sdk/02-gl_call.rst @@ -65,7 +65,7 @@ Payload "CallContract": { "address": Address, // 20-byte target contract address "calldata": Calldata, // Method call in calldata format - "state": Number // Storage type: 0=default, 1=latest_final, 2=latest_non_final + "state": Number // Storage type: 0=default, 1=latest_finalized, 2=latest_decided } } diff --git a/support/tools/genvm-tool/genvm_tool/codegen/go.py b/support/tools/genvm-tool/genvm_tool/codegen/go.py index 818e5a8d..b70a93b0 100644 --- a/support/tools/genvm-tool/genvm_tool/codegen/go.py +++ b/support/tools/genvm-tool/genvm_tool/codegen/go.py @@ -85,6 +85,10 @@ def render(defs: list[Definition], *, go_package: str = 'genvm', **_opts) -> str buf.append('const (\n') for k, v in d.values.items(): buf.append(f'\t{name}{to_camel(k)} {name} = {_dump(v)}\n') + for alias, target in d.aliases.items(): + buf.append( + f'\t{name}{to_camel(alias)} {name} = {name}{to_camel(target)}\n' + ) buf.append(')\n') elif isinstance(d, Const): buf.append(f'const {to_camel(d.name)} = {_dump(d.value)}\n') diff --git a/support/tools/genvm-tool/genvm_tool/codegen/model.py b/support/tools/genvm-tool/genvm_tool/codegen/model.py index a7912d99..3b1b74a4 100644 --- a/support/tools/genvm-tool/genvm_tool/codegen/model.py +++ b/support/tools/genvm-tool/genvm_tool/codegen/model.py @@ -27,6 +27,7 @@ class Enum: name: str repr: str values: dict[str, object] + aliases: dict[str, str] @dataclasses.dataclass @@ -172,7 +173,20 @@ def parse(data) -> list[Definition]: for t in data: kind = t['type'] if kind == 'enum': - defs.append(Enum(t['name'], t['repr'], t['values'])) + values = t['values'] + aliases = t.get('aliases', {}) + if not isinstance(aliases, dict): + raise ValueError(f"enum {t['name']!r} aliases must be an object") + for alias, target in aliases.items(): + if alias in values: + raise ValueError( + f"enum {t['name']!r} alias {alias!r} conflicts with a value" + ) + if target not in values: + raise ValueError( + f"enum {t['name']!r} alias {alias!r} targets unknown value {target!r}" + ) + defs.append(Enum(t['name'], t['repr'], values, aliases)) elif kind == 'const': defs.append(Const(t['name'], t['repr'], t['value'])) elif kind == 'consts': diff --git a/support/tools/genvm-tool/genvm_tool/codegen/python.py b/support/tools/genvm-tool/genvm_tool/codegen/python.py index 6c07c3a5..745d7433 100644 --- a/support/tools/genvm-tool/genvm_tool/codegen/python.py +++ b/support/tools/genvm-tool/genvm_tool/codegen/python.py @@ -102,6 +102,8 @@ def render(defs: list[Definition], **_opts) -> str: buf.append(f'class {to_camel(d.name)}({base}):\n') for k, v in d.values.items(): buf.append(f'\t{k.upper()} = {_pydump(v)}\n') + for alias, target in d.aliases.items(): + buf.append(f'\t{alias.upper()} = {target.upper()} # Deprecated alias\n') elif isinstance(d, Const): buf.append( f'\n\n{d.name.upper()}: typing.Final[{_py_repr(d.repr)}] = {_pydump(d.value)}\n' diff --git a/support/tools/genvm-tool/genvm_tool/codegen/rst.py b/support/tools/genvm-tool/genvm_tool/codegen/rst.py index e21e48a2..aa684618 100644 --- a/support/tools/genvm-tool/genvm_tool/codegen/rst.py +++ b/support/tools/genvm-tool/genvm_tool/codegen/rst.py @@ -36,6 +36,16 @@ def render(defs: list[Definition], rst_anchor_ns: str = '', **_opts) -> str: buf.append(f'{k}\n') buf.append('~' * len(k) + '\n\n') buf.append(f'Value: ``{v}``\n\n') + for alias, target in d.aliases.items(): + buf.append( + f'.. _gvm-def-{ns}enum-value-{_dash(d.name)}-{_dash(alias)}:\n\n' + ) + buf.append(f'{alias}\n') + buf.append('~' * len(alias) + '\n\n') + buf.append( + 'Deprecated alias of ' + f':ref:`gvm-def-{ns}enum-value-{_dash(d.name)}-{_dash(target)}`.\n\n' + ) elif isinstance(d, Const): buf.append(f'.. _gvm-def-{ns}const-{_dash(d.name)}:\n\n') buf.append(f'{d.name}\n') diff --git a/support/tools/genvm-tool/genvm_tool/codegen/rust.py b/support/tools/genvm-tool/genvm_tool/codegen/rust.py index 68e52f03..1465328b 100644 --- a/support/tools/genvm-tool/genvm_tool/codegen/rust.py +++ b/support/tools/genvm-tool/genvm_tool/codegen/rust.py @@ -59,11 +59,17 @@ def _enum(e: Enum) -> str: buf.append(f'#[repr({e.repr})]\n') buf.append(f'pub enum {name} {{\n') for k, v in e.values.items(): + for alias, target in e.aliases.items(): + if target == k: + buf.append(f' #[serde(alias = "{to_camel(alias)}")]\n') buf.append(f' {to_camel(k)} = {_dump(v)},\n') buf.append('}\n') else: buf.append(f'pub enum {name} {{\n') for k in e.values: + for alias, target in e.aliases.items(): + if target == k: + buf.append(f' #[serde(alias = "{to_camel(alias)}")]\n') buf.append(f' {to_camel(k)},\n') buf.append('}\n') buf.append('\n') @@ -75,6 +81,10 @@ def _enum(e: Enum) -> str: and sorted(vals) == list(range(len(vals))) ): buf.append(f' pub const SIZE: usize = {len(vals)};\n') + for alias, target in e.aliases.items(): + buf.append(f' #[deprecated(note = "use `{name}::{to_camel(target)}`")]\n') + buf.append(' #[allow(non_upper_case_globals)]\n') + buf.append(f' pub const {to_camel(alias)}: Self = Self::{to_camel(target)};\n') buf.append(f' pub fn value(self) -> {_rust_repr(e.repr)} {{\n') buf.append(' match self {\n') for k, v in e.values.items(): diff --git a/support/tools/genvm-tool/unit_tests/test_codegen_aliases.py b/support/tools/genvm-tool/unit_tests/test_codegen_aliases.py new file mode 100644 index 00000000..0b8d0c62 --- /dev/null +++ b/support/tools/genvm-tool/unit_tests/test_codegen_aliases.py @@ -0,0 +1,47 @@ +import pytest + +from genvm_tool import codegen + + +ENUM = [ + { + 'type': 'enum', + 'name': 'storage_type', + 'repr': 'u8', + 'values': {'default': 0, 'latest_finalized': 1, 'latest_decided': 2}, + 'aliases': { + 'latest_final': 'latest_finalized', + 'latest_non_final': 'latest_decided', + }, + } +] + + +def test_enum_aliases_render_for_every_language(): + defs = codegen.model.parse(ENUM) + + rust = codegen.render('rust', defs) + assert 'pub const LatestNonFinal: Self = Self::LatestDecided;' in rust + assert '#[serde(alias = "LatestNonFinal")]' in rust + + python = codegen.render('python', defs) + assert 'LATEST_NON_FINAL = LATEST_DECIDED # Deprecated alias' in python + + go = codegen.render('go', defs) + assert 'StorageTypeLatestNonFinal StorageType = StorageTypeLatestDecided' in go + + rst = codegen.render('rst', defs) + assert 'Deprecated alias of :ref:`gvm-def-enum-value-storage-type-latest-decided`.' in rst + + +@pytest.mark.parametrize( + 'aliases, message', + [ + ({'default': 'latest_decided'}, 'conflicts with a value'), + ({'latest_non_final': 'missing'}, 'targets unknown value'), + ], +) +def test_enum_aliases_fail_closed(aliases, message): + data = [{**ENUM[0], 'aliases': aliases}] + with pytest.raises(ValueError, match=message): + codegen.model.parse(data)