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: