From 6fb83c3e06c4478427932da3d8d06945115822e2 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel CORNILLEAU Date: Mon, 7 Sep 2026 19:16:04 +0200 Subject: [PATCH] docs: document originOf, resolveAddress and the alias RPCs Add an Address translation section to both NAC usage pages describing the originOf and resolveAddress gateway functions (Solidity interface and Michelson view types, classification and resolution codes, when origins are recorded, failure behavior). Document the tez_getTezosEthereumAddress / tez_getEthereumTezosAddress JSON-RPC methods on the Michelson RPC reference and cross-link everything from Accounts and Aliases. Co-Authored-By: Claude Fable 5.1 --- docs/evm/nac-usage.md | 67 ++++++++++++++++++++++ docs/michelson/developing/rpc-reference.md | 34 +++++++++++ docs/michelson/nac-usage.md | 52 +++++++++++++++++ docs/overview/accounts-and-aliases.md | 6 ++ 4 files changed, 159 insertions(+) diff --git a/docs/evm/nac-usage.md b/docs/evm/nac-usage.md index 71f02124..9d0c602b 100644 --- a/docs/evm/nac-usage.md +++ b/docs/evm/nac-usage.md @@ -81,6 +81,69 @@ bytes memory michelineResult = gateway.callMichelsonView( A complete worked example (using the low-level `staticcall` pattern) is available in [`crac_michelson_view_staticcall.sol`](https://gitlab.com/tezos/tezos/-/blob/master/etherlink/kernel_latest/solidity_examples/crac_michelson_view_staticcall.sol). +## Address translation + +Cross-interface calls run under the caller's alias (see [Accounts and Aliases](/overview/accounts-and-aliases)). The gateway exposes two `view` functions to translate between native addresses and aliases. Both take the address as a `string` in its printable form (`0x…` hex for the EVM interface, base58check for the Michelson interface) rather than as a Solidity `address`, and identify interfaces by a runtime id: `0` for the Michelson interface, `1` for the EVM interface. + +```solidity +interface INativeAtomicGateway { + function originOf( + string calldata addr, + uint8 sourceRuntime + ) external view returns (uint8 kind, uint8 homeRuntime, string memory nativeAddress); + + function resolveAddress( + string calldata addr, + uint8 sourceRuntime, + uint8 targetRuntime + ) external view returns (bool classified, uint8 res, string memory translated); + + error InvalidRuntimeId(uint8 received); +} +``` + +### `originOf` + +`originOf(addr, sourceRuntime)` returns how the address `addr` of the interface `sourceRuntime` is classified: + +| `kind` | Meaning | `homeRuntime` | `nativeAddress` | +|---|---|---|---| +| `0` (Unknown) | The address is malformed or has not been seen by the kernel yet | `0` | `""` | +| `1` (Native) | An account native to `sourceRuntime` | `sourceRuntime` | `addr` | +| `2` (Alias) | The alias of an account native to the other interface | The interface the account is native to | The native address of that account | + +The kernel records the origin of an account when it is first used: Michelson user accounts (`tz1…`, `tz2…`, `tz3…`) are always Native, Michelson smart contracts are recorded at origination, EVM accounts when they first sign a transaction or when they have code, and aliases when a cross-interface call creates them. In particular, an EVM address that has only received funds is Unknown. + +For example, to check whether the caller is a Michelson account: + +```solidity +(uint8 kind, , string memory native) = gateway.originOf( + Strings.toHexString(msg.sender), // OpenZeppelin helper: lowercase "0x…" string + 1 +); +if (kind == 2) { + // msg.sender is the alias of the Michelson account `native` (tz1… or KT1…) +} +``` + +### `resolveAddress` + +`resolveAddress(addr, sourceRuntime, targetRuntime)` translates the address `addr` of the interface `sourceRuntime` into the corresponding address of the interface `targetRuntime`: + +- If `addr` is malformed or Unknown, `classified` is `false` and the other values are zero. +- If `addr` is Native, `translated` is its alias in `targetRuntime`. +- If `addr` is an Alias, `translated` is the native address it stands for, rather than an alias of the alias. + +`res` is `0` (Recorded) when the returned address already exists on chain and `1` (Derived) when it was computed with the derivation formula but no cross-interface call has created the alias yet. When `sourceRuntime` and `targetRuntime` are equal, a well-formed address is returned unchanged with `res == 0`. + +```solidity +(bool classified, uint8 res, string memory evmAlias) = gateway.resolveAddress( + "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb", 0, 1 +); +// classified == true; evmAlias is the "0x…" EVM alias of the Tezos account; +// res == 1 until a cross-interface call creates the alias +``` + ## Failure behavior ### `callMichelson` @@ -122,6 +185,10 @@ Because `callMichelsonView` must be invoked via `staticcall`, catch failures wit // success == false if the Michelson view reverted or was not found ``` +### `originOf` and `resolveAddress` + +Malformed addresses never revert: they are reported as Unknown (`kind == 0`) or unclassified (`classified == false`). Both functions revert with the custom error `InvalidRuntimeId(uint8)` when a runtime id is neither `0` nor `1`. + ### Infrastructure failures A 5xx response from the Michelson runtime indicates a kernel-internal error (storage I/O failure, host fault). This is treated as a block-level abort rather than a catchable revert, meaning the entire block is rolled back. These failures are not caused by contract logic and are not catchable by EVM code. diff --git a/docs/michelson/developing/rpc-reference.md b/docs/michelson/developing/rpc-reference.md index 128967c3..73801d55 100644 --- a/docs/michelson/developing/rpc-reference.md +++ b/docs/michelson/developing/rpc-reference.md @@ -47,3 +47,37 @@ The following L1 RPCs exhibit different behavior in Etherlink, either t | `GET /context/issuance/expected_issuance` | Returns dummy zero rewards; Etherlink has no token issuance. | | `POST /helpers/scripts/pack_data` | Uses a throwaway dummy context instead of the live chain state; results may differ for gas-sensitive encodings. | | `GET /context/constants` | Several constants differ from mainnet: `minimal_block_delay` = 1 s (the protocol encoding cannot express sub-second periods; actual block cadence follows Etherlink blocks, down to 500 ms under load); `hard_gas_limit_per_operation` = 660,000 gas (the 30M EVM per-transaction gas cap converted at 22 milligas per EVM gas unit); `cost_per_byte` = 1 mutez. | + +## Alias RPCs + +EVM nodes also expose two Etherlink-specific JSON-RPC methods that compute the alias of an address (see [Accounts and Aliases](/overview/accounts-and-aliases#aliases)). They are served on the node's JSON-RPC endpoint (the base URL, alongside the `eth_*` methods), not under `/tezlink`. The alias is derived from the address alone, so these methods work for any address, whether or not it has been used on chain. + +| Method | Parameter | Result | +|---|---|---| +| `tez_getTezosEthereumAddress` | A Michelson address (`tz1…`, `tz2…`, `tz3…`, or `KT1…`) | Its EVM alias (`0x…`) | +| `tez_getEthereumTezosAddress` | An EVM address (`0x…`) | Its Michelson alias (`KT1…`) | + +For example: + +```bash +curl --request POST \ + --url http://localhost:8545 \ + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data ' +{ + "id": 1, + "jsonrpc": "2.0", + "method": "tez_getEthereumTezosAddress", + "params": ["0x1234567890abcdef1234567890abcdef12345678"] +} +' +``` + +```json +{ + "jsonrpc": "2.0", + "result": "KT1CYcsqu3TnW3aA2hYL62ZCtcA484yCG4Zq", + "id": 1 +} +``` diff --git a/docs/michelson/nac-usage.md b/docs/michelson/nac-usage.md index 8af08003..0fa74d1a 100644 --- a/docs/michelson/nac-usage.md +++ b/docs/michelson/nac-usage.md @@ -151,6 +151,54 @@ IF_NONE { … } (* use the returned bytes *) ``` +## Address translation + +Cross-interface calls run under the caller's alias (see [Accounts and Aliases](/overview/accounts-and-aliases)). The gateway exposes two on-chain views to translate between native addresses and aliases. Both take the address as a `string` in its printable form (base58check for the Michelson interface, `0x…` hex for the EVM interface) rather than as a Michelson `address`, and identify interfaces by a `nat` runtime id: `0` for the Michelson interface, `1` for the EVM interface. Michelson has no instruction to convert an `address` to a `string`, so the address to translate must already be available as a `string`, for example as a parameter. + +| View | Parameter type | Return type | +|---|---|---| +| `originOf` | `pair string nat` (address, source runtime) | `or unit (or nat (pair nat string))` | +| `resolveAddress` | `pair string (pair nat nat)` (address, source runtime, target runtime) | `option (pair nat string)` | + +### `originOf` + +`originOf` returns how the address is classified in the source interface: + +| Result | Meaning | +|---|---| +| `Left Unit` | Unknown: the address is malformed or has not been seen by the kernel yet | +| `Right (Left n)` | Native: an account native to the source interface (`n` is the source runtime id) | +| `Right (Right (Pair n addr))` | Alias: the alias of the account `addr`, native to the interface `n` | + +The kernel records the origin of an account when it is first used: user accounts (`tz1…`, `tz2…`, `tz3…`) are always Native, smart contracts are recorded at origination, EVM accounts when they first sign a transaction or when they have code, and aliases when a cross-interface call creates them. In particular, an EVM address that has only received funds is Unknown. + +### `resolveAddress` + +`resolveAddress` translates the address from the source interface into the corresponding address of the target interface: + +| Result | Meaning | +|---|---| +| `None` | The address is malformed or Unknown in the source interface | +| `Some (Pair 0 addr)` | Recorded: `addr` already exists on chain | +| `Some (Pair 1 addr)` | Derived: `addr` was computed with the derivation formula, but no cross-interface call has created the alias yet | + +For a Native address, `addr` is its alias in the target interface. For an Alias, `addr` is the native address it stands for, rather than an alias of the alias. When the source and target runtimes are equal, a well-formed address is returned unchanged as Recorded. + +```michelson +PUSH address "KT18oDJJKXMKhfE1bSuAPGp92pYcwVDiqsPw"; (* the gateway *) +PUSH nat 0; (* target runtime: Michelson *) +PUSH nat 1; (* source runtime: EVM *) +PAIR; +PUSH string "0x1234567890abcdef1234567890abcdef12345678"; +PAIR; (* pair string (pair nat nat) *) +VIEW "resolveAddress" (option (pair nat string)); +IF_NONE + { FAIL } (* view not found *) + { IF_NONE + { … } (* unknown address *) + { … } } (* Pair 0 "KT1CYcsqu3TnW3aA2hYL62ZCtcA484yCG4Zq" if the alias exists, Pair 1 … otherwise *) +``` + ## Return value ### `%call_evm` — callback @@ -265,3 +313,7 @@ For the gas conversion rules and how the forwarded budget is calculated, see [Re ### `staticcall_evm` A view failure (EVM revert, missing view, type mismatch) surfaces as `None` from `VIEW`, which the caller handles with `IF_NONE`. Out-of-gas is the exception: it fails the operation outright rather than returning `None`, so a forwarded-gas exhaustion cannot be silently treated as a missing view. See the outcome table in the [`staticcall_evm`](#staticcall_evm) section above. + +### `originOf` and `resolveAddress` + +Malformed addresses never fail: they are reported as Unknown (`Left Unit`) or `None`. Both views fail the operation with `(Pair "INVALID_RUNTIME_ID" n)` when a runtime id `n` is neither `0` nor `1`. diff --git a/docs/overview/accounts-and-aliases.md b/docs/overview/accounts-and-aliases.md index c60a95a1..e3f255b4 100644 --- a/docs/overview/accounts-and-aliases.md +++ b/docs/overview/accounts-and-aliases.md @@ -45,6 +45,12 @@ kt1_alias = KT1(blake2b_160(utf8("0x" + lowercase_hex(evm_address)))) Michelson's `SOURCE` instruction returns the null address (`tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU`) for operations originating from cross-interface calls — not the actual alias address. This is because Michelson requires the source to be a user account. +## Translating addresses + +Off chain, EVM nodes expose two JSON-RPC methods that apply the formulas above: `tez_getTezosEthereumAddress` and `tez_getEthereumTezosAddress`. See [Alias RPCs](/michelson/developing/rpc-reference#alias-rpcs). + +On chain, both gateways expose `originOf` and `resolveAddress`, which consult the kernel's records rather than only applying the formulas: they tell whether an address is a native account or an alias, map an alias back to the native account it stands for, and tell whether an alias has already been created. See [NAC usage: EVM to Michelson](/evm/nac-usage#address-translation) and [NAC usage: Michelson to EVM](/michelson/nac-usage#address-translation). + ## Tez precision across interfaces The two interfaces use different precision for tez: