diff --git a/Cargo.lock b/Cargo.lock index a6dd65ce..969120b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7268,8 +7268,6 @@ dependencies = [ "log", "orbinum-runtime", "orbinum-zk-core", - "pallet-account-mapping-rpc", - "pallet-account-mapping-runtime-api", "pallet-relayer-rpc", "pallet-relayer-runtime-api", "pallet-shielded-pool-rpc", @@ -7337,8 +7335,6 @@ dependencies = [ "frame-try-runtime", "hex-literal", "libsecp256k1", - "pallet-account-mapping", - "pallet-account-mapping-runtime-api", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -7347,7 +7343,6 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", - "pallet-evm-precompile-account-mapping", "pallet-evm-precompile-balances", "pallet-evm-precompile-curve25519", "pallet-evm-precompile-curve25519-benchmarking", @@ -7455,51 +7450,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "pallet-account-mapping" -version = "0.3.2" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "orbinum-zk-core", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-keystore", - "sp-runtime", -] - -[[package]] -name = "pallet-account-mapping-rpc" -version = "0.1.0" -dependencies = [ - "hex", - "jsonrpsee", - "pallet-account-mapping-runtime-api", - "serde", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-runtime", -] - -[[package]] -name = "pallet-account-mapping-runtime-api" -version = "0.2.0" -dependencies = [ - "pallet-account-mapping", - "parity-scale-codec", - "scale-info", - "serde", - "sp-api", - "sp-core", - "sp-runtime", -] - [[package]] name = "pallet-asset-conversion" version = "27.0.0" @@ -7790,26 +7740,6 @@ dependencies = [ "scale-info", ] -[[package]] -name = "pallet-evm-precompile-account-mapping" -version = "0.2.0" -dependencies = [ - "fp-evm", - "frame-support", - "frame-system", - "pallet-account-mapping", - "pallet-balances", - "pallet-evm", - "pallet-timestamp", - "parity-scale-codec", - "precompile-utils", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-evm-precompile-balances" version = "0.1.0" @@ -8399,7 +8329,7 @@ dependencies = [ [[package]] name = "pallet-zk-verifier" -version = "0.9.0" +version = "0.10.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index 94bbce89..1688fb8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,10 @@ members = [ "frame/evm-polkavm/proc-macro", "frame/evm-polkavm/uapi", "frame/hotfix-sufficients", - "frame/account-mapping", - "frame/account-mapping/runtime-api", - "frame/account-mapping/rpc", "frame/relayer/runtime-api", "frame/relayer/rpc", "frame/zk-verifier/runtime-api", "frame/zk-verifier/rpc", - "frame/evm/precompile/account-mapping", "client/api", "client/consensus", "client/rpc-core", @@ -230,9 +226,6 @@ orbinum-zk-core = { path = "primitives/zk-core", default-features = false } orbinum-zk-verifier = { path = "primitives/zk-verifier", default-features = false } # Frontier FRAME -pallet-account-mapping = { path = "frame/account-mapping", default-features = false } -pallet-account-mapping-rpc = { path = "frame/account-mapping/rpc" } -pallet-account-mapping-runtime-api = { path = "frame/account-mapping/runtime-api", default-features = false } pallet-base-fee = { path = "frame/base-fee", default-features = false } pallet-dynamic-fee = { path = "frame/dynamic-fee", default-features = false } pallet-ethereum = { path = "frame/ethereum", default-features = false } @@ -241,7 +234,6 @@ pallet-evm-chain-id = { path = "frame/evm-chain-id", default-features = false } pallet-evm-polkavm = { path = "frame/evm-polkavm", default-features = false } pallet-evm-polkavm-proc-macro = { path = "frame/evm-polkavm/proc-macro" } pallet-evm-polkavm-uapi = { path = "frame/evm-polkavm/uapi", default-features = false } -pallet-evm-precompile-account-mapping = { path = "frame/evm/precompile/account-mapping", default-features = false } pallet-evm-precompile-balances = { path = "frame/evm/precompile/balances", default-features = false } pallet-evm-precompile-curve25519 = { path = "frame/evm/precompile/curve25519", default-features = false } pallet-evm-precompile-curve25519-benchmarking = { path = "frame/evm/precompile/curve25519/benchmarking", default-features = false } diff --git a/README.md b/README.md index 0a045087..35ec04a1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Orbinum is built using Substrate's FRAME framework and implements Clean Architec - **Pallets**: Modular runtime components (`pallet-shielded-pool`, `pallet-zk-verifier`, `pallet-relayer`) - **Primitives**: Core cryptographic libraries (`zk-core`, `zk-verifier`, `zk-circuits`) - **Client**: RPC layer and blockchain infrastructure -- **Circuits**: Circom zero-knowledge circuits (`value_proof`, `transfer`, `unshield`, `private_link`) +- **Circuits**: Circom zero-knowledge circuits (`value_proof`, `transfer`, `unshield`) ## License diff --git a/deny.toml b/deny.toml index 1ed4cceb..4072177e 100644 --- a/deny.toml +++ b/deny.toml @@ -27,6 +27,7 @@ ignore = [ "RUSTSEC-2026-0020", # guest-controlled resource exhaustion in WASI implementations "RUSTSEC-2026-0021", # panic adding excessive fields to wasi:http/types.fields "RUSTSEC-2026-0085", # panic when lifting flags component value + "RUSTSEC-2026-0222", # stores can mix up type indices between engines # tracing-subscriber 0.2.25 — pinned by ark-relations (ZK crates), no upgrade path "RUSTSEC-2025-0055", diff --git a/docs/BENCHMARKING_STEPS.md b/docs/BENCHMARKING_STEPS.md index 5cdb14ba..f49ee1ca 100644 --- a/docs/BENCHMARKING_STEPS.md +++ b/docs/BENCHMARKING_STEPS.md @@ -80,7 +80,6 @@ This overwrites the four committed `weights.rs` files: | Pallet | Output | |--------|--------| -| `pallet_account_mapping` | `frame/account-mapping/src/weights.rs` | | `pallet_zk_verifier` | `frame/zk-verifier/src/weights.rs` | | `pallet_shielded_pool` | `frame/shielded-pool/src/weights.rs` | | `pallet_relayer` | `frame/relayer/src/weights.rs` | diff --git a/frame/account-mapping/CHANGELOG.md b/frame/account-mapping/CHANGELOG.md deleted file mode 100644 index 2058bb32..00000000 --- a/frame/account-mapping/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog - -All notable changes to `pallet-account-mapping` will be documented in this file. - -## [0.3.2] - 2026-07-09 - -### Changed - -- Weights regenerated on the benchmark host (AMD EPYC-Genoa, 32 GB). No logic - change — refreshed alongside the per-note circuit-version release. - -## [0.3.1] - 2026-06-01 - -### Changed - -- Updated FRAME benchmark weights measured on Hetzner CCX33 (AMD EPYC-Milan, 8 vCPU dedicated, 32 GB RAM). Steps: `50`, Repeat: `20`. All extrinsics refreshed. - -## [0.3.0] - 2026-05-29 - -### Added - -- **`Config::NativeEvmChainId: Get`** — new required pallet constant. Declares the native EVM chain ID of the runtime (e.g. `2700` for Orbinum testnet). Used by `dispatch_as_linked_account` to reject calls where the chain link targets the native EVM chain. **Breaking: all existing `Config` implementations must add this constant.** -- **`Config::is_implicit_evm_account(account: &Self::AccountId) -> bool`** — new optional hook with default `false`. Runtimes using `OrbinumSignature` (Fase 3) should override this to return `true` for secp256k1 accounts (where `AccountId32 = [H160 | 0x00×12]`), enabling the storage-free mapping path. Non-secp256k1 runtimes require no change. -- **`Error::UseNativeSignatureForEvmAccounts`** — returned by `dispatch_as_linked_account` when `chain_id == T::NativeEvmChainId::get()`. Secp256k1 accounts sign Substrate extrinsics directly via `OrbinumSignature`; no chain-link dispatch is needed or allowed for native EVM links. - -### Changed - -- **`map_account`** — for accounts where `is_implicit_evm_account` returns `true`, the call is now event-only: emits `AccountMapped` without writing to `OriginalAccounts` or `MappedAccounts` storage. The H160 mapping is structurally derivable from the `AccountId32` suffix and requires no storage entry. Sr25519/Ed25519 accounts are unaffected. -- **`unmap_account`** — for implicit secp256k1 accounts, cleans up any legacy storage entry from before Fase 3 if one exists, then emits `AccountUnmapped` and returns. If no legacy entry exists, derives the address implicitly. Sr25519/Ed25519 path is unchanged. -- **`dispatch_as_linked_account`** — added guard: rejects calls where `chain_id == T::NativeEvmChainId::get()` with `UseNativeSignatureForEvmAccounts`. -- **`benchmarking.rs`** — `evm_account()` helper seed byte changed from `0x42` to `100` for consistent behavior between the production `AccountId32` and the `u64` test mock. `map_account` benchmark assertion updated: post-Fase-1, secp256k1 accounts do not write to `OriginalAccounts`, so the assertion is now `assert!(!OriginalAccounts::::contains_key(&caller))`. - -## [0.2.1] - 2026-05-22 - -### Changed - -- Updated FRAME benchmark weights measured on Hetzner CCX33 (AMD EPYC-Milan, 8 vCPU dedicated, 32 GB RAM). Steps: `50`, Repeat: `20`. All 16 extrinsics refreshed. - -### Fixed - -- `benchmarking.rs`: replaced `T::AccountId: From<[u8; 32]>` bound (which broke compilation in mock runtimes with `type AccountId = u64`) with a `scale_codec::Decode`-based helper. The `evm_account()` function now decodes a 32-byte slice into `T::AccountId` without requiring the `From<[u8; 32]>` trait bound. - -## [0.2.0] - 2026-05-14 - -### Added - -- Initial tracked release. -- EVM ↔ Substrate account mapping via `AccountMapping` storage. -- `link_evm_address` and `unlink_evm_address` extrinsics. -- Full benchmark and weight coverage. diff --git a/frame/account-mapping/Cargo.toml b/frame/account-mapping/Cargo.toml deleted file mode 100644 index 828c2401..00000000 --- a/frame/account-mapping/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "pallet-account-mapping" -version = "0.3.2" -license = "Apache-2.0" -description = "Stateful mapping between Substrate AccountId32 and EVM H160 addresses, with alias registration and multichain identity support." -authors = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -scale-codec = { workspace = true } -scale-info = { workspace = true } - -frame-benchmarking = { workspace = true, optional = true } -frame-support = { workspace = true } -frame-system = { workspace = true } - -orbinum-zk-core = { workspace = true, features = ["poseidon-native"] } -pallet-balances = { workspace = true, optional = true } -serde = { workspace = true, optional = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } - -[dev-dependencies] -pallet-balances = { workspace = true, features = ["default"] } -sp-io = { workspace = true, features = ["default"] } -sp-keystore = { workspace = true, features = ["std"] } - -[features] -default = ["std"] -std = [ - "orbinum-zk-core/std", - "scale-codec/std", - "scale-info/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "pallet-balances/std", - "serde/std", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", -] diff --git a/frame/account-mapping/rpc/Cargo.toml b/frame/account-mapping/rpc/Cargo.toml deleted file mode 100644 index b2808d7b..00000000 --- a/frame/account-mapping/rpc/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "pallet-account-mapping-rpc" -version = "0.1.0" -authors = { workspace = true } -edition = { workspace = true } -license = "Apache-2.0" - -[dependencies] -hex = { workspace = true } -jsonrpsee = { workspace = true, features = ["server", "macros", "client"] } -pallet-account-mapping-runtime-api = { path = "../runtime-api" } -serde = { workspace = true, features = ["derive"] } -sp-api = { workspace = true } -sp-blockchain = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } diff --git a/frame/account-mapping/rpc/src/error.rs b/frame/account-mapping/rpc/src/error.rs deleted file mode 100644 index 07a675e6..00000000 --- a/frame/account-mapping/rpc/src/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Error codes and constructors for the `pallet-account-mapping` RPC server. -//! -//! Centralising the codes here prevents magic values from scattering across -//! the RPC `lib.rs` and makes them easy to change without touching business logic. - -use jsonrpsee::types::ErrorObjectOwned; - -/// Pallet-specific JSON-RPC error codes. -/// -/// Reserved range: 4000–4099 (conflict-free with standard JSON-RPC 2.0 errors -/// and with other pallets that use the 4100+ range). -pub mod code { - /// Invalid H160 input (malformed hex or incorrect length). - pub const INVALID_H160: i32 = 4000; - /// Invalid AccountId32 input (malformed hex or incorrect length). - pub const INVALID_ACCOUNT_ID: i32 = 4001; - /// Generic Runtime API error (failure when calling the runtime). - pub const RUNTIME_ERROR: i32 = 4002; - /// The provided alias has an invalid format. - pub const INVALID_ALIAS: i32 = 4003; - /// The external chain address has an invalid format. - pub const INVALID_CHAIN_ADDRESS: i32 = 4004; -} - -// ───────────────────────────────────────────────────────────────────────────── -// Typed error constructors -// ───────────────────────────────────────────────────────────────────────────── - -pub fn invalid_h160(detail: impl core::fmt::Display) -> ErrorObjectOwned { - ErrorObjectOwned::owned( - code::INVALID_H160, - format!("Invalid H160 address: {detail}"), - None::<()>, - ) -} - -pub fn invalid_account_id(detail: impl core::fmt::Display) -> ErrorObjectOwned { - ErrorObjectOwned::owned( - code::INVALID_ACCOUNT_ID, - format!("Invalid AccountId32: {detail}"), - None::<()>, - ) -} - -pub fn runtime_error(detail: impl core::fmt::Display) -> ErrorObjectOwned { - ErrorObjectOwned::owned( - code::RUNTIME_ERROR, - format!("Runtime API error: {detail}"), - None::<()>, - ) -} - -pub fn invalid_alias(detail: impl core::fmt::Display) -> ErrorObjectOwned { - ErrorObjectOwned::owned( - code::INVALID_ALIAS, - format!("Invalid alias: {detail}"), - None::<()>, - ) -} - -pub fn invalid_chain_address(detail: impl core::fmt::Display) -> ErrorObjectOwned { - ErrorObjectOwned::owned( - code::INVALID_CHAIN_ADDRESS, - format!("Invalid chain address: {detail}"), - None::<()>, - ) -} diff --git a/frame/account-mapping/rpc/src/lib.rs b/frame/account-mapping/rpc/src/lib.rs deleted file mode 100644 index 6fbcfc92..00000000 --- a/frame/account-mapping/rpc/src/lib.rs +++ /dev/null @@ -1,487 +0,0 @@ -use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObjectOwned}; -use pallet_account_mapping_runtime_api::{AccountMappingRuntimeApi, AliasInfo, ListingInfo}; -use serde::{Deserialize, Serialize}; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_core::{crypto::AccountId32, H160}; -use sp_runtime::traits::Block as BlockT; -use std::sync::Arc; - -pub mod error; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AccountAddresses { - pub mapped: Option, - pub fallback: Option, -} - -/// Alias resolution response returned to the frontend. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AliasResponse { - /// Alias string (without @). - pub alias: String, - /// Substrate AccountId32 (hex). - pub substrate_account: String, - /// EVM H160 address (hex). - pub evm_address: Option, - /// How many external chain links are registered. - pub chain_links_count: u32, -} - -/// Marketplace listing info returned to the frontend. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ListingResponse { - /// Alias string (without @). - pub alias: String, - /// Asking price as a string (u128 as string to avoid JS overflow). - pub price: String, - /// True if a whitelist is set (private/OTC sale). - pub private: bool, - /// Number of accounts in the whitelist (0 if public). - pub whitelist_count: u32, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ChainLinkResponse { - pub chain_id: u32, - pub address: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AccountMetadataResponse { - pub display_name: Option, - pub bio: Option, - pub avatar: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct FullIdentityResponse { - pub owner: String, - pub evm_address: Option, - pub chain_links: Vec, - pub metadata: Option, -} - -/// A private chain link commitment returned to the frontend. -/// Real address is never included. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PrivateLinkResponse { - /// Chain ID this private link belongs to. - pub chain_id: u32, - /// Keccak256 commitment (hex-encoded, 0x-prefixed). - pub commitment: String, -} - -#[rpc(client, server)] -pub trait AccountMappingApi { - #[method(name = "accountMapping_getMappedAccount")] - fn get_mapped_account(&self, address: String) -> RpcResult>; - - #[method(name = "accountMapping_getAccountAddresses")] - fn get_account_addresses(&self, account_id: String) -> RpcResult; - - /// Resolve "@alias" → identity info. - /// Accepts the alias with or without the "@" prefix. - #[method(name = "accountMapping_resolveAlias")] - fn resolve_alias(&self, alias: String) -> RpcResult>; - - /// Return the alias registered for a given SS58 / hex AccountId. - #[method(name = "accountMapping_getAliasOf")] - fn get_alias_of(&self, account_id: String) -> RpcResult>; - - // ── Marketplace queries ──────────────────────────────────────────────── - - /// Get listing info for a given alias. - /// Returns null if the alias is not listed for sale. - #[method(name = "accountMapping_getListingInfo")] - fn get_listing_info(&self, alias: String) -> RpcResult>; - - /// Get the active listing for the account that owns the alias, if any. - /// Useful to show "Alias for sale" on the address detail page. - #[method(name = "accountMapping_getAccountListing")] - fn get_account_listing(&self, account_id: String) -> RpcResult>; - - /// Check whether a specific buyer can purchase the given alias right now. - /// Returns false if not for sale, buyer already has alias, or buyer not in whitelist. - #[method(name = "accountMapping_canBuy")] - fn can_buy(&self, alias: String, buyer_account_id: String) -> RpcResult; - - /// Resolve "@alias" → Full identity (links, metadata, addresses). - #[method(name = "accountMapping_resolveFullIdentity")] - fn resolve_full_identity(&self, alias: String) -> RpcResult>; - - /// Get metadata (profile) for a specific account. - #[method(name = "accountMapping_getAccountMetadata")] - fn get_account_metadata( - &self, - account_id: String, - ) -> RpcResult>; - - /// Get the owner AccountId of a verified multichain link. - #[method(name = "accountMapping_getLinkOwner")] - fn get_link_owner(&self, chain_id: u32, address: String) -> RpcResult>; - - /// Get all supported blockchains and their signature schemes. - #[method(name = "accountMapping_getSupportedChains")] - fn get_supported_chains( - &self, - ) -> RpcResult>; - - // ── Private link queries ──────────────────────────────────────────────── - - /// Return the private link commitments registered for an alias. - /// Real addresses are never exposed. Returns null if alias does not exist. - #[method(name = "accountMapping_getPrivateLinks")] - fn get_private_links(&self, alias: String) -> RpcResult>>; - - /// Check whether a specific commitment exists as a private link for the alias. - #[method(name = "accountMapping_hasPrivateLink")] - fn has_private_link(&self, alias: String, commitment: String) -> RpcResult; -} - -pub struct AccountMapping { - client: Arc, - _marker: std::marker::PhantomData, -} - -impl AccountMapping { - pub fn new(client: Arc) -> Self { - Self { - client, - _marker: Default::default(), - } - } -} - -fn parse_h160(hex_address: &str) -> Result { - let bytes = hex::decode(hex_address.trim_start_matches("0x")).map_err(error::invalid_h160)?; - if bytes.len() != 20 { - return Err(error::invalid_h160("address must be exactly 20 bytes")); - } - Ok(H160::from_slice(&bytes)) -} - -fn parse_account_id32(hex_account: &str) -> Result { - let bytes = - hex::decode(hex_account.trim_start_matches("0x")).map_err(error::invalid_account_id)?; - if bytes.len() != 32 { - return Err(error::invalid_account_id( - "AccountId must be exactly 32 bytes", - )); - } - AccountId32::try_from(bytes.as_slice()) - .map_err(|_| error::invalid_account_id("failed to parse AccountId32")) -} - -fn alias_info_to_response(alias: String, info: AliasInfo) -> AliasResponse { - let owner_bytes: &[u8; 32] = info.owner.as_ref(); - let owner_hex = format!("0x{}", hex::encode(owner_bytes)); - let evm_hex = info - .evm_address - .map(|a| format!("0x{}", hex::encode(a.as_bytes()))); - AliasResponse { - alias, - substrate_account: owner_hex, - evm_address: evm_hex, - chain_links_count: info.chain_links_count, - } -} - -fn listing_info_to_response( - alias: String, - info: ListingInfo, -) -> ListingResponse { - ListingResponse { - alias, - price: info.price.to_string(), - private: info.private, - whitelist_count: info.whitelist_count, - } -} - -impl AccountMappingApiServer for AccountMapping -where - B: BlockT, - C: ProvideRuntimeApi + HeaderBackend + 'static, - C::Api: AccountMappingRuntimeApi, -{ - fn get_mapped_account(&self, address: String) -> RpcResult> { - let parsed = parse_h160(&address)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let account = api - .get_mapped_account(best_block, parsed) - .map_err(|e| error::runtime_error(e))?; - - Ok(account.map(|a| { - let bytes: &[u8; 32] = a.as_ref(); - format!("0x{}", hex::encode(bytes)) - })) - } - - fn get_account_addresses(&self, account_id: String) -> RpcResult { - let account = parse_account_id32(&account_id)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let mapped = api - .get_mapped_address(best_block, account.clone()) - .map_err(|e| error::runtime_error(e))? - .map(|h| format!("0x{}", hex::encode(h.as_bytes()))); - - let fallback = api - .get_fallback_address(best_block, account) - .map_err(|e| error::runtime_error(e))? - .map(|h| format!("0x{}", hex::encode(h.as_bytes()))); - - Ok(AccountAddresses { mapped, fallback }) - } - - fn resolve_alias(&self, alias: String) -> RpcResult> { - // Strip leading "@" if present. - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let info = api - .resolve_alias(best_block, alias_bytes) - .map_err(|e| error::runtime_error(e))?; - - Ok(info.map(|i| alias_info_to_response(clean.to_string(), i))) - } - - fn get_alias_of(&self, account_id: String) -> RpcResult> { - let account = parse_account_id32(&account_id)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let alias_bytes = api - .get_alias_of(best_block, account) - .map_err(|e| error::runtime_error(e))?; - - Ok(alias_bytes.and_then(|b| String::from_utf8(b).ok())) - } - - fn get_listing_info(&self, alias: String) -> RpcResult> { - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let info = api - .get_listing_info(best_block, alias_bytes) - .map_err(|e| error::runtime_error(e))?; - - Ok(info.map(|i| listing_info_to_response(clean.to_string(), i))) - } - - fn get_account_listing(&self, account_id: String) -> RpcResult> { - let account = parse_account_id32(&account_id)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let result = api - .get_account_listing(best_block, account) - .map_err(|e| error::runtime_error(e))?; - - Ok(result.and_then(|(alias_bytes, info)| { - let alias = String::from_utf8(alias_bytes).ok()?; - Some(listing_info_to_response(alias, info)) - })) - } - - fn can_buy(&self, alias: String, buyer_account_id: String) -> RpcResult { - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - let buyer = parse_account_id32(&buyer_account_id)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let ok = api - .can_buy(best_block, alias_bytes, buyer) - .map_err(|e| error::runtime_error(e))?; - - Ok(ok) - } - - fn resolve_full_identity(&self, alias: String) -> RpcResult> { - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let info = api - .get_full_identity(best_block, alias_bytes) - .map_err(|e| error::runtime_error(e))?; - - Ok(info.map(|i| { - let owner_bytes: &[u8; 32] = i.owner.as_ref(); - FullIdentityResponse { - owner: format!("0x{}", hex::encode(owner_bytes)), - evm_address: i - .evm_address - .map(|a| format!("0x{}", hex::encode(a.as_bytes()))), - chain_links: i - .chain_links - .into_iter() - .map(|l| ChainLinkResponse { - chain_id: l.chain_id, - address: format!("0x{}", hex::encode(&l.address)), - }) - .collect(), - metadata: i.metadata.map(|m| AccountMetadataResponse { - display_name: m.display_name.and_then(|v| String::from_utf8(v).ok()), - bio: m.bio.and_then(|v| String::from_utf8(v).ok()), - avatar: m.avatar.and_then(|v| String::from_utf8(v).ok()), - }), - } - })) - } - - fn get_account_metadata( - &self, - account_id: String, - ) -> RpcResult> { - let account = parse_account_id32(&account_id)?; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let metadata = api - .get_account_metadata(best_block, account) - .map_err(|e| error::runtime_error(e))?; - - Ok(metadata.map(|m| AccountMetadataResponse { - display_name: m.display_name.and_then(|v| String::from_utf8(v).ok()), - bio: m.bio.and_then(|v| String::from_utf8(v).ok()), - avatar: m.avatar.and_then(|v| String::from_utf8(v).ok()), - })) - } - - fn get_link_owner(&self, chain_id: u32, address: String) -> RpcResult> { - let parsed_address = if let Some(stripped) = address.strip_prefix("0x") { - hex::decode(stripped).map_err(error::invalid_chain_address)? - } else { - address.as_bytes().to_vec() - }; - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let owner = api - .get_link_owner(best_block, chain_id, parsed_address) - .map_err(|e| error::runtime_error(e))?; - - Ok(owner.map(|a| { - let bytes: &[u8; 32] = a.as_ref(); - format!("0x{}", hex::encode(bytes)) - })) - } - - fn get_supported_chains( - &self, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - api.get_supported_chains(best_block) - .map_err(|e| error::runtime_error(e)) - } - - fn get_private_links(&self, alias: String) -> RpcResult>> { - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - let links = api - .get_private_links(best_block, alias_bytes) - .map_err(|e| error::runtime_error(e))?; - - Ok(links.map(|list| { - list.into_iter() - .map(|l| PrivateLinkResponse { - chain_id: l.chain_id, - commitment: format!("0x{}", hex::encode(l.commitment)), - }) - .collect() - })) - } - - fn has_private_link(&self, alias: String, commitment: String) -> RpcResult { - let clean = alias.trim_start_matches('@'); - let alias_bytes = clean.as_bytes().to_vec(); - - // Parsear el commitment hex a [u8; 32]. - let bytes = hex::decode(commitment.trim_start_matches("0x")) - .map_err(|e| error::invalid_chain_address(format!("invalid commitment hex: {e}")))?; - if bytes.len() != 32 { - return Err(error::invalid_chain_address( - "commitment must be exactly 32 bytes", - )); - } - let mut commitment_arr = [0u8; 32]; - commitment_arr.copy_from_slice(&bytes); - - let api = self.client.runtime_api(); - let best_block = self.client.info().best_hash; - - api.has_private_link(best_block, alias_bytes, commitment_arr) - .map_err(|e| error::runtime_error(e)) - } -} - -#[cfg(test)] -mod tests { - use super::{parse_account_id32, parse_h160}; - - #[test] - fn parse_h160_accepts_valid_hex() { - let parsed = parse_h160("0x1111111111111111111111111111111111111111").unwrap(); - assert_eq!( - format!("0x{}", hex::encode(parsed.as_bytes())), - "0x1111111111111111111111111111111111111111" - ); - } - - #[test] - fn parse_h160_rejects_wrong_length() { - let result = parse_h160("0x1111"); - assert!(result.is_err()); - } - - #[test] - fn parse_account_id32_accepts_valid_hex() { - let parsed = parse_account_id32( - "0x0101010101010101010101010101010101010101010101010101010101010101", - ) - .unwrap(); - let bytes: &[u8; 32] = parsed.as_ref(); - assert_eq!(bytes[0], 0x01); - assert_eq!(bytes[31], 0x01); - } - - #[test] - fn parse_account_id32_rejects_wrong_length() { - let result = parse_account_id32("0x0101"); - assert!(result.is_err()); - } - - #[test] - fn resolve_alias_strips_at_prefix() { - let alias = "@nolasco"; - let clean = alias.trim_start_matches('@'); - assert_eq!(clean, "nolasco"); - } -} diff --git a/frame/account-mapping/runtime-api/Cargo.toml b/frame/account-mapping/runtime-api/Cargo.toml deleted file mode 100644 index 4859c4ef..00000000 --- a/frame/account-mapping/runtime-api/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "pallet-account-mapping-runtime-api" -version = "0.2.0" -authors = { workspace = true } -edition = { workspace = true } -license = "Apache-2.0" - -[dependencies] -pallet-account-mapping = { path = "../", default-features = false } -scale-codec = { workspace = true, default-features = false, features = ["derive"] } -scale-info = { workspace = true, default-features = false, features = ["derive"] } -serde = { workspace = true, default-features = false, features = ["derive", "alloc"] } -sp-api = { workspace = true, default-features = false } -sp-core = { workspace = true, default-features = false } -sp-runtime = { workspace = true, default-features = false } - -[features] -default = ["std"] -std = [ - "scale-codec/std", - "scale-info/std", - "serde/std", - "sp-api/std", - "sp-core/std", - "sp-runtime/std", - "pallet-account-mapping/std", -] diff --git a/frame/account-mapping/runtime-api/src/lib.rs b/frame/account-mapping/runtime-api/src/lib.rs deleted file mode 100644 index 30bbeaa1..00000000 --- a/frame/account-mapping/runtime-api/src/lib.rs +++ /dev/null @@ -1,171 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -use alloc::vec::Vec; -use scale_info::TypeInfo; -use sp_core::H160; - -/// A private chain link — only the commitment is stored on-chain (address never revealed). -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct PrivateLink { - pub chain_id: u32, - pub commitment: [u8; 32], -} - -/// Compact alias info returned from the runtime API. -/// A link to an address on an external chain. -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct ChainLink { - pub chain_id: u32, - pub address: Vec, -} - -/// Metadata for an account's public profile. -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct AccountMetadata { - pub display_name: Option>, - pub bio: Option>, - pub avatar: Option>, -} - -/// Compact alias info returned from the runtime API. -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct AliasInfo { - pub owner: AccountId, - pub evm_address: Option, - pub chain_links_count: u32, -} - -/// Full identity info including links and metadata. -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct FullIdentityInfo { - pub owner: AccountId, - pub evm_address: Option, - pub chain_links: Vec, - pub metadata: Option, -} - -/// Compact listing info returned from the runtime API. -#[derive( - scale_codec::Encode, - scale_codec::Decode, - Clone, - PartialEq, - Eq, - Debug, - TypeInfo -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct ListingInfo { - /// Asking price in native tokens. - pub price: Balance, - /// True if a buyer whitelist is set (private OTC sale). - pub private: bool, - /// How many accounts are in the whitelist (0 if public). - pub whitelist_count: u32, -} - -pub use pallet_account_mapping::SignatureScheme; - -sp_api::decl_runtime_apis! { - pub trait AccountMappingRuntimeApi - where - AccountId: scale_codec::Codec, - Balance: scale_codec::Codec, - { - fn get_mapped_account(address: H160) -> Option; - fn get_mapped_address(account: AccountId) -> Option; - fn get_fallback_address(account: AccountId) -> Option; - - /// Resolve an alias string to its identity info. - /// Returns None if the alias is not registered. - fn resolve_alias(alias: Vec) -> Option>; - - /// Return the alias registered for a given AccountId, if any. - fn get_alias_of(account: AccountId) -> Option>; - - // ── Marketplace queries ──────────────────────────────────────────── - - /// Get sale listing info for a given alias. - /// Returns None if the alias is not listed for sale. - fn get_listing_info(alias: Vec) -> Option>; - - /// Return the alias listed for sale by the given account, if any. - /// Returns (alias_bytes, listing_info) or None. - fn get_account_listing(account: AccountId) -> Option<(Vec, ListingInfo)>; - - /// Check if a given buyer can purchase the specified alias. - /// Returns false if: alias not for sale, buyer already has an alias, - /// or buyer is not in the whitelist. - fn can_buy(alias: Vec, buyer: AccountId) -> bool; - - /// Get full identity info for an alias. - fn get_full_identity(alias: Vec) -> Option>; - - /// Get metadata for a specific account. - fn get_account_metadata(account: AccountId) -> Option; - - /// Get the owner of a verified multichain link. - fn get_link_owner(chain_id: u32, address: Vec) -> Option; - - /// Get all supported chains and their signature schemes. - fn get_supported_chains() -> Vec<(u32, SignatureScheme)>; - - // ── Private link queries ─────────────────────────────────────────── - - /// Return the list of private chain link commitments for an alias. - /// The real addresses are never stored on-chain. - /// Returns None if the alias does not exist. - fn get_private_links(alias: Vec) -> Option>; - - /// Check whether a specific commitment is registered as a private link - /// for the given alias. - fn has_private_link(alias: Vec, commitment: [u8; 32]) -> bool; - } -} diff --git a/frame/account-mapping/src/benchmarking.rs b/frame/account-mapping/src/benchmarking.rs deleted file mode 100644 index 60bb3230..00000000 --- a/frame/account-mapping/src/benchmarking.rs +++ /dev/null @@ -1,583 +0,0 @@ -//! Benchmarks for `pallet-account-mapping`. -//! -//! Run with: -//! ```sh -//! ./scripts/benchmark.sh pallet_account_mapping -//! ``` -//! or directly: -//! ```sh -//! cargo run --release --features runtime-benchmarks -- \ -//! benchmark pallet \ -//! --pallet pallet_account_mapping \ -//! --extrinsic "*" \ -//! --steps 50 --repeat 20 \ -//! --output frame/account-mapping/src/weights.rs \ -//! --template scripts/frame-weight-template.hbs -//! ``` - -use super::*; -use crate::pallet::{ - AccountAliases, AccountMetadatas, AliasListings, Identities, MappedAccounts, OriginalAccounts, - SupportedChains, -}; -use frame_benchmarking::v2::*; -use frame_support::traits::{Currency, Get, ReservableCurrency}; -use frame_system::RawOrigin; -use scale_codec::{Decode, Encode}; -use sp_core::crypto::KeyTypeId; -use sp_runtime::traits::Convert; - -// KeyTypeId reserved for ECDSA signatures in benchmarks. -// Does not collide with any production KeyTypeId (all use standard 4-char ASCII). -const BENCH_KEY: KeyTypeId = KeyTypeId(*b"bktp"); - -// Fixture: short valid alias of realistic maximum length for benchmarks. -// Uses `b"bench_alias_xx"` where xx varies to avoid collisions between benchmarks. - -fn make_alias(seed: &[u8]) -> AliasOf { - let mut bytes: alloc::vec::Vec = seed.to_vec(); - // Pad to exactly 14 bytes — zeros are not valid [a-z0-9_], - // so we use 'a' as padding. - while bytes.len() < 14 { - bytes.push(b'a'); - } - bytes.truncate(14); - bytes - .try_into() - .expect("14-byte alias always fits within MaxAliasLength ≥ 14") -} - -fn fund_account(who: &T::AccountId) { - let amount = T::AliasDeposit::get() * 100u32.into(); - let _ = T::Currency::make_free_balance_be(who, amount); -} - -fn register_alias_for(who: &T::AccountId, alias: AliasOf) { - fund_account::(who); - let deposit = T::AliasDeposit::get(); - T::Currency::reserve(who, deposit).expect("sufficient balance"); - - let evm_address = T::AccountIdToEvmAddress::convert(who.clone()); - let record = crate::pallet::IdentityRecord:: { - owner: who.clone(), - evm_address, - deposit, - chain_links: Default::default(), - }; - Identities::::insert(&alias, record); - AccountAliases::::insert(who, &alias); -} - -#[benchmarks( - where - ::RuntimeCall: From>, -)] -mod benchmarks { - use super::*; - - /// Returns an EVM-compatible `AccountId` for benchmarks. - /// - /// Uses SCALE `Decode` so no `From<[u8; 32]>` bound is required, keeping - /// compatibility with the test mock (`AccountId = u64`). - /// - /// - Production (`AccountId32`): decodes 32 bytes → `[100, 0u8×31]`; - /// bytes[20..32] == `[0u8;12]` → `is_implicit_evm_account` = true. ✓ - /// - Test mock (`u64`): reads first 8 LE bytes → `100u64`; - /// `is_implicit_evm_account(100)` = true (mock convention). ✓ - /// - /// Using 100 ensures consistent behavior between mock and production: - /// both treat this account as an implicit secp256k1 account (Fase 1). - fn evm_account() -> T::AccountId { - let mut bytes = [0u8; 32]; - bytes[0] = 100; - T::AccountId::decode(&mut &bytes[..]).unwrap_or_else(|_| whitelisted_caller()) - } - - // ─── map_account ──────────────────────────────────────────────────────── - - #[benchmark] - fn map_account() { - // Uses a secp256k1 implicit account (is_implicit_evm_account = true). - // Post-Fase-1: map_account emits AccountMapped but does NOT write - // OriginalAccounts — the mapping is derived deterministically from - // the AccountId32 itself and requires no storage. - let caller = evm_account::(); - fund_account::(&caller); - - #[extrinsic_call] - map_account(RawOrigin::Signed(caller.clone())); - - // Secp256k1 accounts bypass storage — only an event is emitted. - assert!(!OriginalAccounts::::contains_key(&caller)); - } - - // ─── unmap_account ────────────────────────────────────────────────────── - - #[benchmark] - fn unmap_account() { - // Needs an EVM-compatible AccountId (bytes[20..32] == [0u8; 12]). - let caller = evm_account::(); - fund_account::(&caller); - - // Pre-state: account already mapped. - let address = T::AccountIdToEvmAddress::convert(caller.clone()) - .expect("evm_account always has an EVM address"); - OriginalAccounts::::insert(&caller, address); - MappedAccounts::::insert(address, &caller); - - #[extrinsic_call] - unmap_account(RawOrigin::Signed(caller.clone())); - - assert!(!OriginalAccounts::::contains_key(&caller)); - } - - // ─── register_alias ───────────────────────────────────────────────────── - - #[benchmark] - fn register_alias() { - let caller: T::AccountId = whitelisted_caller(); - fund_account::(&caller); - let alias = make_alias::(b"reg_bench"); - - #[extrinsic_call] - register_alias(RawOrigin::Signed(caller.clone()), alias.clone()); - - assert!(Identities::::contains_key(&alias)); - assert!(AccountAliases::::contains_key(&caller)); - } - - // ─── release_alias ────────────────────────────────────────────────────── - - #[benchmark] - fn release_alias() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"rel_bench"); - register_alias_for::(&caller, alias.clone()); - - #[extrinsic_call] - release_alias(RawOrigin::Signed(caller.clone())); - - assert!(!AccountAliases::::contains_key(&caller)); - assert!(!Identities::::contains_key(&alias)); - } - - // ─── transfer_alias ───────────────────────────────────────────────────── - - #[benchmark] - fn transfer_alias() { - let sender: T::AccountId = whitelisted_caller(); - let receiver: T::AccountId = account("receiver", 0, 0); - fund_account::(&receiver); - - let alias = make_alias::(b"txfr_bench"); - register_alias_for::(&sender, alias.clone()); - - #[extrinsic_call] - transfer_alias(RawOrigin::Signed(sender.clone()), receiver.clone()); - - assert!(AccountAliases::::contains_key(&receiver)); - assert!(!AccountAliases::::contains_key(&sender)); - } - - // ─── put_alias_on_sale ────────────────────────────────────────────────── - - #[benchmark] - fn put_alias_on_sale() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"sale_bench"); - register_alias_for::(&caller, alias.clone()); - - let price = T::AliasDeposit::get() * 10u32.into(); - - #[extrinsic_call] - put_alias_on_sale(RawOrigin::Signed(caller.clone()), price, None); - - assert!(AliasListings::::contains_key(&alias)); - } - - // ─── cancel_sale ──────────────────────────────────────────────────────── - - #[benchmark] - fn cancel_sale() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"cncl_bench"); - register_alias_for::(&caller, alias.clone()); - - let price = T::AliasDeposit::get() * 10u32.into(); - let listing = crate::pallet::SaleListing:: { - price, - allowed_buyers: None, - }; - AliasListings::::insert(&alias, listing); - - #[extrinsic_call] - cancel_sale(RawOrigin::Signed(caller.clone())); - - assert!(!AliasListings::::contains_key(&alias)); - } - - // ─── buy_alias ────────────────────────────────────────────────────────── - - #[benchmark] - fn buy_alias() { - // Seller with listed alias. - let seller: T::AccountId = account("seller", 0, 0); - let alias = make_alias::(b"buy_bench"); - register_alias_for::(&seller, alias.clone()); - - let price = T::AliasDeposit::get() * 5u32.into(); - let listing = crate::pallet::SaleListing:: { - price, - allowed_buyers: None, - }; - AliasListings::::insert(&alias, listing); - - // Buyer with enough funds (price + deposit). - let buyer: T::AccountId = whitelisted_caller(); - let buyer_funds = T::AliasDeposit::get() * 50u32.into(); - let _ = T::Currency::make_free_balance_be(&buyer, buyer_funds); - - #[extrinsic_call] - buy_alias(RawOrigin::Signed(buyer.clone()), alias.clone()); - - assert!(AccountAliases::::contains_key(&buyer)); - assert!(!AccountAliases::::contains_key(&seller)); - assert!(!AliasListings::::contains_key(&alias)); - } - - // ─── add_chain_link ───────────────────────────────────────────────────── - // - // Uses `sp_io::crypto` (host functions) to generate and sign, compatible - // with no_std/WASM without requiring sp-core/full_crypto. - - #[benchmark] - fn add_chain_link() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"link_bench"); - register_alias_for::(&caller, alias.clone()); - - SupportedChains::::insert(1u32, SignatureScheme::Eip191); - - // Generate a key pair in the keystore with a deterministic seed. - let pubkey = sp_io::crypto::ecdsa_generate(BENCH_KEY, Some(b"//bench_link_01".to_vec())); - - let account_bytes = caller.encode(); - let final_hash = crate::utils::eip191_message_hash(&account_bytes); - - // Sign using host function — compatible with WASM and no_std. - let sig = sp_io::crypto::ecdsa_sign_prehashed(BENCH_KEY, &pubkey, &final_hash) - .expect("signing must succeed with a freshly generated key"); - let sig_bytes: [u8; 65] = sig.0; - - // Derive the Ethereum address from the generated key pair. - let full_pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, &final_hash) - .ok() - .expect("recovery must succeed with a valid signature"); - let addr_hash = sp_io::hashing::keccak_256(&full_pubkey[..]); - let eth_addr: crate::ExternalAddr = addr_hash[12..] - .to_vec() - .try_into() - .expect("20 bytes always fit in ExternalAddr"); - - #[extrinsic_call] - add_chain_link( - RawOrigin::Signed(caller.clone()), - 1u32, - eth_addr.into_inner(), - sig_bytes.to_vec(), - ); - - let record = Identities::::get(&alias).expect("identity must exist"); - assert_eq!(record.chain_links.len(), 1); - } - - // ─── remove_chain_link ────────────────────────────────────────────────── - - #[benchmark] - fn remove_chain_link() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"rmlink_bnch"); - register_alias_for::(&caller, alias.clone()); - - // Insert a chain link directly into storage (bypasses verification). - let mut record = Identities::::get(&alias).expect("identity must exist"); - let link_addr: crate::ExternalAddr = b"0xbenchmarkaddr12345678" - .to_vec() - .try_into() - .expect("addr fits in ExternalAddr"); - record - .chain_links - .try_push(crate::ChainLink { - chain_id: 1, - address: link_addr, - }) - .expect("MAX_CHAIN_LINKS > 0"); - Identities::::insert(&alias, record); - - #[extrinsic_call] - remove_chain_link(RawOrigin::Signed(caller.clone()), 1u32); - - let record = Identities::::get(&alias).expect("identity must exist"); - assert!(record.chain_links.is_empty()); - } - - // ─── set_account_metadata ─────────────────────────────────────────────── - - #[benchmark] - fn set_account_metadata() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"meta_bench"); - register_alias_for::(&caller, alias.clone()); - - let display_name: Option< - frame_support::BoundedVec< - u8, - frame_support::pallet_prelude::ConstU32<{ crate::MAX_METADATA_LEN }>, - >, - > = Some(b"Benchmark User".to_vec().try_into().expect("short name")); - let bio: Option< - frame_support::BoundedVec< - u8, - frame_support::pallet_prelude::ConstU32<{ crate::MAX_METADATA_LEN }>, - >, - > = Some( - b"Benchmark bio string for testing" - .to_vec() - .try_into() - .expect("short bio"), - ); - - #[extrinsic_call] - set_account_metadata(RawOrigin::Signed(caller.clone()), display_name, bio, None); - - assert!(AccountMetadatas::::contains_key(&caller)); - } - - // ─── dispatch_as_linked_account ────────────────────────────────────────── - // - // Benchmark of the proxy call with host functions (WASM-compatible). - - #[benchmark] - fn dispatch_as_linked_account() { - let owner: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"dispatch_bnch"); - register_alias_for::(&owner, alias.clone()); - SupportedChains::::insert(1u32, crate::SignatureScheme::Eip191); - - // Generate a key pair in the keystore. - let pubkey = - sp_io::crypto::ecdsa_generate(BENCH_KEY, Some(b"//bench_dispatch_01".to_vec())); - - // Sign the owner's AccountId (to verify ownership of the chain link). - let account_bytes = owner.encode(); - let final_hash = crate::utils::eip191_message_hash(&account_bytes); - let sig = sp_io::crypto::ecdsa_sign_prehashed(BENCH_KEY, &pubkey, &final_hash) - .expect("valid signing"); - let sig_bytes: [u8; 65] = sig.0; - - let full_pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, &final_hash) - .ok() - .expect("valid recovery"); - let addr_hash = sp_io::hashing::keccak_256(&full_pubkey[..]); - let eth_addr: crate::ExternalAddr = addr_hash[12..] - .to_vec() - .try_into() - .expect("20 bytes fit in ExternalAddr"); - - // Insert the chain link directly (bypasses add_chain_link). - let mut record = crate::pallet::Identities::::get(&alias).expect("identity exists"); - record - .chain_links - .try_push(crate::ChainLink { - chain_id: 1, - address: eth_addr.clone(), - }) - .expect("free slot"); - crate::pallet::Identities::::insert(&alias, record); - crate::pallet::ReverseChainLinks::::insert((1u32, eth_addr.clone()), &owner); - - // Prepare the inner call (empty remark) and its EIP-191 signature. - let inner_call: ::RuntimeCall = - frame_system::Call::::remark { - remark: Default::default(), - } - .into(); - let payload = inner_call.encode(); - let call_hash = crate::utils::eip191_message_hash(&payload); - let sig2 = sp_io::crypto::ecdsa_sign_prehashed(BENCH_KEY, &pubkey, &call_hash) - .expect("valid signing of inner call"); - let sig2_bytes: [u8; 65] = sig2.0; - - let relayer: T::AccountId = account("relayer", 0, 0); - fund_account::(&relayer); - - #[extrinsic_call] - dispatch_as_linked_account( - frame_system::RawOrigin::Signed(relayer), - owner.clone(), - 1u32, - addr_hash[12..].to_vec(), - sig2_bytes.to_vec(), - alloc::boxed::Box::new(inner_call), - ); - } - - // ─── register_private_link ─────────────────────────────────────────────── - - #[benchmark] - fn register_private_link() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"prv_reg_bench"); - register_alias_for::(&caller, alias.clone()); - - let commitment = sp_io::hashing::keccak_256(b"benchmark_private_link_commitment"); - - #[extrinsic_call] - register_private_link(RawOrigin::Signed(caller.clone()), 501u32, commitment); - - let links = crate::pallet::PrivateChainLinks::::get(&alias); - assert_eq!(links.len(), 1); - assert_eq!(links[0].commitment, commitment); - } - - // ─── remove_private_link ───────────────────────────────────────────────── - - #[benchmark] - fn remove_private_link() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"prv_rm_bench"); - register_alias_for::(&caller, alias.clone()); - - let commitment = sp_io::hashing::keccak_256(b"benchmark_remove_private_link"); - let link = crate::PrivateChainLink { - chain_id: 502u32, - commitment, - }; - let mut links = crate::pallet::PrivateChainLinks::::get(&alias); - links.try_push(link).expect("free slot"); - crate::pallet::PrivateChainLinks::::insert(&alias, links); - - #[extrinsic_call] - remove_private_link(RawOrigin::Signed(caller.clone()), commitment); - - assert!(crate::pallet::PrivateChainLinks::::get(&alias).is_empty()); - } - - // ─── reveal_private_link ───────────────────────────────────────────────── - // - // The chain must be in `SupportedChains` for this benchmark because - // `reveal_private_link` verifies the signature from the external wallet. - - #[benchmark] - fn reveal_private_link() { - let caller: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"prv_rev_bench"); - register_alias_for::(&caller, alias.clone()); - SupportedChains::::insert(1u32, crate::SignatureScheme::Eip191); - - // Generate a key pair in the keystore with a deterministic seed. - let pubkey = sp_io::crypto::ecdsa_generate(BENCH_KEY, Some(b"//bench_reveal_01".to_vec())); - - let account_bytes = caller.encode(); - let final_hash = crate::utils::eip191_message_hash(&account_bytes); - let sig = sp_io::crypto::ecdsa_sign_prehashed(BENCH_KEY, &pubkey, &final_hash) - .expect("valid signing"); - let sig_bytes: [u8; 65] = sig.0; - - let full_pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig_bytes, &final_hash) - .ok() - .expect("valid recovery"); - let addr_hash = sp_io::hashing::keccak_256(&full_pubkey[..]); - let addr_bytes = addr_hash[12..].to_vec(); - - // Compute commitment: Poseidon2(Poseidon2(chain_id_fe, addr_fe), blinding_fe) - // This must match the on-chain verification in reveal_private_link. - let blinding = [0x11u8; 32]; - let mut chain_id_fe = [0u8; 32]; - chain_id_fe[..4].copy_from_slice(&1u32.to_le_bytes()); - let mut addr_fe = [0u8; 32]; - let copy_len = addr_bytes.len().min(32); - addr_fe[..copy_len].copy_from_slice(&addr_bytes[..copy_len]); - let inner = orbinum_zk_core::host_interface::poseidon_host_interface::poseidon_hash_2( - &chain_id_fe, - &addr_fe, - ); - let commitment: [u8; 32] = - orbinum_zk_core::host_interface::poseidon_host_interface::poseidon_hash_2( - &inner, &blinding, - ) - .try_into() - .expect("Poseidon output is 32 bytes"); - - // Insert the private link into storage. - let link = crate::PrivateChainLink { - chain_id: 1u32, - commitment, - }; - let mut prv_links = crate::pallet::PrivateChainLinks::::get(&alias); - prv_links.try_push(link).expect("free slot"); - crate::pallet::PrivateChainLinks::::insert(&alias, prv_links); - - #[extrinsic_call] - reveal_private_link( - RawOrigin::Signed(caller.clone()), - commitment, - addr_bytes, - blinding, - sig_bytes.to_vec(), - ); - - // The private link must have been promoted to a public chain link. - assert!(crate::pallet::PrivateChainLinks::::get(&alias).is_empty()); - let record = crate::pallet::Identities::::get(&alias).expect("identity exists"); - assert_eq!(record.chain_links.len(), 1); - } - - // ─── dispatch_as_private_link ──────────────────────────────────────────── - // - // The mock verifier in benchmarks accepts any non-empty proof with byte[0] == 0x01. - // In production the real verifier (pallet-zk-verifier) takes longer; this measures - // the extrinsic coordination overhead without the ZK cost. - - #[benchmark] - fn dispatch_as_private_link() { - let owner: T::AccountId = whitelisted_caller(); - let alias = make_alias::(b"prv_disp_bnch"); - register_alias_for::(&owner, alias.clone()); - - let commitment = sp_io::hashing::keccak_256(b"benchmark_dispatch_private"); - let link = crate::PrivateChainLink { - chain_id: 503u32, - commitment, - }; - let mut prv_links = crate::pallet::PrivateChainLinks::::get(&alias); - prv_links.try_push(link).expect("free slot"); - crate::pallet::PrivateChainLinks::::insert(&alias, prv_links); - - // Proof marked as valid by MockPrivateLinkVerifier (byte[0] == 0x01). - let mut zk_proof = alloc::vec![0u8; 64]; - zk_proof[0] = 0x01; - - let inner_call: ::RuntimeCall = - frame_system::Call::::remark { - remark: Default::default(), - } - .into(); - - let relayer: T::AccountId = account("relayer", 0, 0); - fund_account::(&relayer); - - #[extrinsic_call] - dispatch_as_private_link( - frame_system::RawOrigin::Signed(relayer), - owner.clone(), - commitment, - zk_proof, - alloc::boxed::Box::new(inner_call), - ); - } - - // ─── Benchmark test suite wiring ───────────────────────────────────────── - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); -} diff --git a/frame/account-mapping/src/lib.rs b/frame/account-mapping/src/lib.rs deleted file mode 100644 index 708f58be..00000000 --- a/frame/account-mapping/src/lib.rs +++ /dev/null @@ -1,1308 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -pub use pallet::*; - -// ── External modules to the FRAME pallet ─────────────────────────────────────── - -/// ZK port: `PrivateLinkVerifierPort` + stub `DisabledPrivateLinkVerifier`. -pub mod ports; -pub use ports::{DisabledPrivateLinkVerifier, PrivateLinkVerifierPort}; - -/// Non-generic types, constants, and structs (no dependency on `T: Config`). -pub mod types; -pub use types::*; - -/// Validation logic and cryptography decoupled from the pallet state. -pub mod utils; - -/// Trait and weight estimates for all extrinsics. -pub mod weights; -pub use weights::WeightInfo; - -#[cfg(test)] -pub mod mock; - -#[cfg(test)] -mod tests; - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; - -/// Implementations of Runtime API functions over the pallet storage. -/// Used from `impl_runtime_apis!` in the node runtime. -pub mod runtime_api_impl; - -use sp_core::H160; - -// ── Pallet FRAME ───────────────────────────────────────────────────────────── - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use alloc::{boxed::Box, vec::Vec}; - use frame_support::sp_runtime::traits::{Convert, Dispatchable, Zero}; - use frame_support::{ - dispatch::GetDispatchInfo, - pallet_prelude::*, - traits::{Currency, ExistenceRequirement, ReservableCurrency, UnfilteredDispatchable}, - Blake2_128Concat, Parameter, - }; - use frame_system::pallet_prelude::*; - - // ── Generic types (depend on T: Config) ────────────────────────────────────── - - pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - - /// Alias: UTF-8 string bounded by `T::MaxAliasLength`. - pub type AliasOf = BoundedVec::MaxAliasLength>; - - /// Sale listing for an alias. - #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(T))] - pub struct SaleListing { - /// Asking price in native ORB. - pub price: BalanceOf, - /// Optional whitelist. `None` = public sale. `Some` = private/OTC sale. - pub allowed_buyers: - Option>>, - } - - /// Identity record stored per alias. - #[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen - )] - #[scale_info(skip_type_params(T))] - pub struct IdentityRecord { - /// AccountId32 owner of this alias. - pub owner: T::AccountId, - /// Derived EVM H160 address (None for purely native accounts). - pub evm_address: Option, - /// Balance reserved as deposit for this alias. - pub deposit: BalanceOf, - /// Links to external chains (Bitcoin, Solana, …). - pub chain_links: BoundedVec>, - } - - // ── Pallet ──────────────────────────────────────────────────────────────── - - #[pallet::pallet] - pub struct Pallet(_); - - // ── Config ──────────────────────────────────────────────────────────────── - - #[pallet::config] - pub trait Config: frame_system::Config>> { - /// Currency used for alias deposits. - type Currency: ReservableCurrency; - - /// Converts a Substrate AccountId to its equivalent EVM H160, if one exists. - type AccountIdToEvmAddress: Convert>; - - /// Minimum deposit required to register an alias. - #[pallet::constant] - type AliasDeposit: Get>; - - /// Maximum alias length in bytes. - #[pallet::constant] - type MaxAliasLength: Get; - - /// Weights. Use [`weights::SubstrateWeight`] as a reference; - /// replace with real benchmarks in production. - type WeightInfo: crate::WeightInfo; - - /// Verifier for ZK proofs of `dispatch_as_private_link` (Phase 2). - /// Use [`DisabledPrivateLinkVerifier`] until the circuit is deployed. - type PrivateLinkVerifier: crate::PrivateLinkVerifierPort; - - /// Returns `true` if `account` is an implicit EVM-suffix account whose H160 - /// address is always derivable from the `AccountId` without a storage lookup. - /// - /// Override this in runtimes that use `OrbinumSignature` (Phase 3): secp256k1 - /// accounts produce `AccountId32 = [H160 | 0x00×12]`, making `map_account` - /// storage writes redundant for them. - /// - /// Default: `false` — conservative fallback that preserves existing behaviour - /// and keeps mock/test runtimes working without changes. - fn is_implicit_evm_account(_account: &Self::AccountId) -> bool { - false - } - - /// EVM chain ID of this runtime's native EVM (e.g. 2700 for Orbinum testnet). - /// - /// `dispatch_as_linked_account` rejects calls where `chain_id` equals this value: - /// secp256k1 accounts can sign Substrate extrinsics directly via `OrbinumSignature`, - /// so delegating through a chain link is redundant and confusing. - #[pallet::constant] - type NativeEvmChainId: Get; - - /// Runtime call type (for `dispatch_as_linked_account` and `dispatch_as_private_link`). - type RuntimeCall: Parameter - + Dispatchable - + GetDispatchInfo - + UnfilteredDispatchable; - } - - // ────────────────────────────────────────────── - // Storage - // ────────────────────────────────────────────── - - /// EVM H160 → AccountId32 (stateful mapping). - #[pallet::storage] - #[pallet::getter(fn mapped_account)] - pub type MappedAccounts = - StorageMap<_, Blake2_128Concat, H160, T::AccountId, OptionQuery>; - - /// AccountId32 → EVM H160 (reverse index). - #[pallet::storage] - #[pallet::getter(fn mapped_address)] - pub type OriginalAccounts = - StorageMap<_, Blake2_128Concat, T::AccountId, H160, OptionQuery>; - - /// Alias (bytes) → IdentityRecord. - #[pallet::storage] - #[pallet::getter(fn identity_of)] - pub type Identities = - StorageMap<_, Blake2_128Concat, AliasOf, IdentityRecord, OptionQuery>; - - /// Mapping of ChainId (u32) to its supported signature scheme. - #[pallet::storage] - #[pallet::getter(fn supported_chain)] - pub type SupportedChains = - StorageMap<_, Blake2_128Concat, ChainId, SignatureScheme, OptionQuery>; - - /// AccountId32 → Alias (reverse index for uniqueness enforcement). - #[pallet::storage] - #[pallet::getter(fn alias_of)] - pub type AccountAliases = - StorageMap<_, Blake2_128Concat, T::AccountId, AliasOf, OptionQuery>; - - /// Alias → sale listing. Present only when alias is listed for sale. - #[pallet::storage] - #[pallet::getter(fn listing_price)] - pub type AliasListings = - StorageMap<_, Blake2_128Concat, AliasOf, SaleListing, OptionQuery>; - - /// AccountId32 → AccountMetadata (profile details). - #[pallet::storage] - #[pallet::getter(fn account_metadata)] - pub type AccountMetadatas = - StorageMap<_, Blake2_128Concat, T::AccountId, AccountMetadata, OptionQuery>; - - /// (ChainId, ExternalAddr) → AccountId32 (Reverse link index). - #[pallet::storage] - #[pallet::getter(fn link_owner)] - pub type ReverseChainLinks = - StorageMap<_, Blake2_128Concat, (ChainId, ExternalAddr), T::AccountId, OptionQuery>; - - /// Alias → private chain link commitments. - /// No address is ever stored here — only Poseidon commitments. - /// Coexists with the public `chain_links` inside `IdentityRecord`. - #[pallet::storage] - #[pallet::getter(fn private_chain_links)] - pub type PrivateChainLinks = StorageMap< - _, - Blake2_128Concat, - AliasOf, - BoundedVec>, - ValueQuery, - >; - - // ────────────────────────────────────────────── - // Events - // ────────────────────────────────────────────── - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A stateful EVM mapping was registered. - AccountMapped { - account: T::AccountId, - address: H160, - }, - /// A stateful EVM mapping was removed. - AccountUnmapped { - account: T::AccountId, - address: H160, - }, - /// A human-readable alias was registered. - AliasRegistered { - account: T::AccountId, - alias: AliasOf, - evm_address: Option, - }, - /// An alias was released and the deposit returned. - AliasReleased { - account: T::AccountId, - alias: AliasOf, - }, - /// An alias was transferred to a new owner. - AliasTransferred { - from: T::AccountId, - to: T::AccountId, - alias: AliasOf, - }, - /// An alias was listed for sale. - AliasListedForSale { - seller: T::AccountId, - alias: AliasOf, - price: BalanceOf, - /// True if a buyer whitelist was set. - private: bool, - }, - /// A sale listing was cancelled by the owner. - AliasSaleCancelled { - seller: T::AccountId, - alias: AliasOf, - }, - /// An alias was sold: price paid to seller, deposit reserved from buyer. - AliasSold { - seller: T::AccountId, - buyer: T::AccountId, - alias: AliasOf, - price: BalanceOf, - }, - /// A chain link was added via signature verification. - ChainLinkAdded { - account: T::AccountId, - chain_id: ChainId, - address: ExternalAddr, - }, - /// A chain link was removed. - ChainLinkRemoved { - account: T::AccountId, - chain_id: ChainId, - }, - /// Account metadata (profile) was updated. - MetadataUpdated { account: T::AccountId }, - /// A new chain support was added by governance. - SupportedChainAdded { - chain_id: ChainId, - scheme: SignatureScheme, - }, - /// A chain support was removed by governance. - SupportedChainRemoved { chain_id: ChainId }, - /// A call was executed on behalf of a linked account. - ProxyCallExecuted { - owner: T::AccountId, - chain_id: ChainId, - address: ExternalAddr, - }, - /// A private chain link commitment was registered. Address never revealed. - PrivateChainLinkAdded { - account: T::AccountId, - chain_id: ChainId, - commitment: [u8; 32], - }, - /// A private chain link commitment was removed by its owner. - PrivateChainLinkRemoved { - account: T::AccountId, - chain_id: ChainId, - commitment: [u8; 32], - }, - /// A private chain link commitment was revealed and promoted to a public chain link. - /// Once emitted, the address is permanently public. - PrivateChainLinkRevealed { - account: T::AccountId, - chain_id: ChainId, - address: ExternalAddr, - }, - /// A call was dispatched using a private link without revealing the wallet address. - /// The commitment identifies which private slot was used; the real address is never exposed. - PrivateLinkDispatchExecuted { - owner: T::AccountId, - commitment: [u8; 32], - }, - } - - // ────────────────────────────────────────────── - // Errors - // ────────────────────────────────────────────── - - #[pallet::error] - pub enum Error { - /// The account already has a stateful EVM mapping. - AlreadyMapped, - /// The EVM address is already mapped to another account. - AddressAlreadyMapped, - /// The account has no stateful EVM mapping to remove. - NotMapped, - /// The requested alias is already taken. - AliasTaken, - /// The account already owns an alias; release it first. - AlreadyHasAlias, - /// The account does not own any alias. - NoAlias, - /// Alias contains invalid characters (only [a-z0-9_] allowed). - InvalidAliasCharacters, - /// Alias is too short (minimum 3 bytes). - AliasTooShort, - /// The destination account already owns an alias. - NewOwnerAlreadyHasAlias, - /// Cannot transfer an alias to yourself. - CannotTransferToSelf, - /// The alias is not listed for sale. - NotForSale, - /// The alias is already listed for sale. - AlreadyForSale, - /// Sale price cannot be zero. - PriceCannotBeZero, - /// Buyer already owns an alias. - BuyerAlreadyHasAlias, - /// The buyer is not in the seller's whitelist for this alias. - NotInWhitelist, - /// The provided whitelist exceeds the maximum allowed size. - WhitelistTooLarge, - /// Native Substrate accounts (without EVM mapping) cannot be mapped blindly. - NativeAccountCannotBeMapped, - /// Invalid signature for the provided external address. - InvalidSignature, - /// The provided chain_id is not yet supported for signature verification. - UnsupportedChain, - /// The chain link already exists for this account. - ChainLinkAlreadyExists, - /// The chain link does not exist. - ChainLinkNotFound, - /// Too many external links (limit reached). - TooManyLinks, - /// The chain_id is already registered in the system. - ChainAlreadySupported, - /// The provided external address is not linked to the requested owner. - NotOwnerOfLink, - /// A private link with the same chain_id or commitment already exists. - PrivateLinkAlreadyExists, - /// No private link found with the given commitment. - PrivateLinkNotFound, - /// Too many private links registered (limit reached). - TooManyPrivateLinks, - /// The provided (address, blinding) do not produce the stored commitment. - CommitmentMismatch, - /// The ZK proof for a private link dispatch is invalid. - InvalidProof, - /// Attempted to register a Substrate-native signature scheme as a chain link scheme. - /// Chain links are exclusively for external, non-Substrate ecosystems. - SubstrateNativeSchemeNotAllowed, - /// Attempting to use `dispatch_as_linked_account` with the native EVM chain ID. - /// Secp256k1 accounts can sign Substrate extrinsics directly via `OrbinumSignature`. - UseNativeSignatureForEvmAccounts, - } - - // ────────────────────────────────────────────── - // Calls - // ────────────────────────────────────────────── - - #[pallet::call] - impl Pallet { - /// Add support for a new blockchain signature scheme. - /// - /// Only Callable by Root/Governance. - #[pallet::call_index(11)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn add_supported_chain( - origin: OriginFor, - chain_id: ChainId, - scheme: SignatureScheme, - ) -> DispatchResult { - ensure_root(origin)?; - // Chain links are exclusively for external, non-Substrate-native ecosystems. - // Substrate accounts are already self-proving via their AccountId32; linking - // them with a chain link is meaningless and would create a confusing identity - // model. See SignatureScheme::is_for_external_chain() for the invariant. - ensure!( - scheme.is_for_external_chain(), - Error::::SubstrateNativeSchemeNotAllowed - ); - SupportedChains::::insert(chain_id, scheme.clone()); - Self::deposit_event(Event::SupportedChainAdded { chain_id, scheme }); - Ok(()) - } - - /// Remove support for a blockchain. - /// - /// Only Callable by Root/Governance. - #[pallet::call_index(12)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn remove_supported_chain(origin: OriginFor, chain_id: ChainId) -> DispatchResult { - ensure_root(origin)?; - SupportedChains::::remove(chain_id); - Self::deposit_event(Event::SupportedChainRemoved { chain_id }); - Ok(()) - } - /// Register a stateful EVM → Substrate mapping using the fallback H160. - /// This is the "lite" mapping for power users who don't need an alias. - /// - /// For secp256k1 accounts in runtimes that use `OrbinumSignature` (Phase 3), - /// the H160 mapping is implicit and always derivable from the `AccountId`. - /// `map_account` is still callable by those accounts — it emits the - /// `AccountMapped` event (useful for indexers) without writing to storage. - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::map_account())] - pub fn map_account(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - let address = T::AccountIdToEvmAddress::convert(who.clone()) - .ok_or(Error::::NativeAccountCannotBeMapped)?; - - // Secp256k1 post-OrbinumSignature: AccountId32 = [H160 | 0x00×12]. - // The mapping is implicit and deterministic — no storage entry needed. - // Emit the event so indexers can record the association. - if T::is_implicit_evm_account(&who) { - Self::deposit_event(Event::AccountMapped { - account: who, - address, - }); - return Ok(()); - } - - // Sr25519 / Ed25519: explicit storage entry required. - ensure!( - !OriginalAccounts::::contains_key(&who), - Error::::AlreadyMapped - ); - ensure!( - !MappedAccounts::::contains_key(address), - Error::::AddressAlreadyMapped - ); - - OriginalAccounts::::insert(&who, address); - MappedAccounts::::insert(address, &who); - - Self::deposit_event(Event::AccountMapped { - account: who, - address, - }); - - Ok(()) - } - - /// Remove a stateful EVM mapping. - /// - /// For implicit secp256k1 accounts (`OrbinumSignature` runtimes), no storage - /// entry exists. `unmap_account` is still callable — it emits `AccountUnmapped` - /// (useful for indexers) without touching storage. - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::unmap_account())] - pub fn unmap_account(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Secp256k1 post-OrbinumSignature: no storage entry was written by map_account. - // Handle both the case where the account migrated from a pre-Phase-3 storage - // entry and the case where there is no entry at all. - if T::is_implicit_evm_account(&who) { - let address = if let Some(addr) = OriginalAccounts::::take(&who) { - // Legacy entry from before OrbinumSignature — clean it up. - MappedAccounts::::remove(addr); - addr - } else { - // No storage entry (expected post-Phase-3); derive address implicitly. - T::AccountIdToEvmAddress::convert(who.clone()) - .ok_or(Error::::NativeAccountCannotBeMapped)? - }; - Self::deposit_event(Event::AccountUnmapped { - account: who, - address, - }); - return Ok(()); - } - - let Some(address) = OriginalAccounts::::take(&who) else { - return Err(Error::::NotMapped.into()); - }; - - MappedAccounts::::remove(address); - - Self::deposit_event(Event::AccountUnmapped { - account: who, - address, - }); - - Ok(()) - } - - /// Register a human-readable alias. - /// - /// Rules: - /// - Alias must be 3–`MaxAliasLength` bytes long. - /// - Only lowercase alphanumeric characters and underscores: [a-z0-9_]. - /// - Requires reserving `AliasDeposit` ORB to prevent spam. - /// - Each account can hold at most one alias at a time. - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::register_alias())] - pub fn register_alias(origin: OriginFor, alias: AliasOf) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Validate format via utils (decoupled from the pallet state). - crate::utils::validate_alias(&alias).map_err(|e| match e { - crate::utils::AliasValidationError::TooShort => Error::::AliasTooShort, - crate::utils::AliasValidationError::InvalidCharacters => { - Error::::InvalidAliasCharacters - } - })?; - - // One alias per account. - ensure!( - !AccountAliases::::contains_key(&who), - Error::::AlreadyHasAlias - ); - - // Alias must be unclaimed. - ensure!( - !Identities::::contains_key(&alias), - Error::::AliasTaken - ); - - // Reserve the deposit. - let deposit = T::AliasDeposit::get(); - T::Currency::reserve(&who, deposit)?; - - // Derive EVM address if available. - let evm_address = T::AccountIdToEvmAddress::convert(who.clone()); - - let record = IdentityRecord:: { - owner: who.clone(), - evm_address, - deposit, - chain_links: BoundedVec::default(), - }; - - Identities::::insert(&alias, record); - AccountAliases::::insert(&who, &alias); - - Self::deposit_event(Event::AliasRegistered { - account: who, - alias, - evm_address, - }); - - Ok(()) - } - - /// Release an alias, unreserving the deposit. - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::release_alias())] - pub fn release_alias(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::take(&who).ok_or(Error::::NoAlias)?; - - let record = Identities::::take(&alias).ok_or(Error::::NoAlias)?; - - // Clean up reverse chain links. - for link in record.chain_links.iter() { - ReverseChainLinks::::remove((link.chain_id, link.address.clone())); - } - - // Clean up private chain link commitments (addresses never revealed). - PrivateChainLinks::::remove(&alias); - - // [C-1 FIX] Remove the active listing so the alias cannot be - // purchased after being re-registered by another user. - AliasListings::::remove(&alias); - - // Return the reserved deposit. - T::Currency::unreserve(&who, record.deposit); - - Self::deposit_event(Event::AliasReleased { - account: who, - alias, - }); - - Ok(()) - } - - /// Transfer ownership of an alias to another account. - /// - /// Rules: - /// - The caller must own the alias. - /// - The destination account must not already own an alias. - /// - The deposit is moved atomically: unreserved from sender, reserved from recipient. - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::transfer_alias())] - pub fn transfer_alias(origin: OriginFor, new_owner: T::AccountId) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Cannot transfer to yourself. - ensure!(who != new_owner, Error::::CannotTransferToSelf); - - // Destination must not already have an alias. - ensure!( - !AccountAliases::::contains_key(&new_owner), - Error::::NewOwnerAlreadyHasAlias, - ); - - // Take the alias from the current owner. - let alias = AccountAliases::::take(&who).ok_or(Error::::NoAlias)?; - - // Load the identity record. - let mut record = Identities::::get(&alias).ok_or(Error::::NoAlias)?; - - // [C-2 FIX] Cancel the active listing: the new owner must not - // inherit a sale they did not authorize. - AliasListings::::remove(&alias); - - // Move the deposit: unreserve from old owner, reserve from new owner. - // [H-1 FIX] Manual rollback is incorrect; FRAME automatically reverts - // storage if the extrinsic returns Err. - T::Currency::unreserve(&who, record.deposit); - T::Currency::reserve(&new_owner, record.deposit)?; - - // Update the record with new owner and re-derived EVM address. - let new_evm = T::AccountIdToEvmAddress::convert(new_owner.clone()); - record.owner = new_owner.clone(); - record.evm_address = new_evm; - - // Write back. - Identities::::insert(&alias, record.clone()); - AccountAliases::::insert(&new_owner, &alias); - - // Update reverse chain links to new owner. - for link in record.chain_links.iter() { - ReverseChainLinks::::insert((link.chain_id, link.address.clone()), &new_owner); - } - - Self::deposit_event(Event::AliasTransferred { - from: who, - to: new_owner, - alias, - }); - - Ok(()) - } - - /// List your alias for sale at a fixed price. - /// - /// - `price`: asking price in native ORB. Must be > 0. - /// - `allowed_buyers`: optional whitelist of up to `MAX_WHITELIST_SIZE` addresses. - /// Pass `None` for a public listing (anyone can buy). - /// Pass `Some(vec![…])` to restrict to specific buyers (private/OTC sale). - /// - While listed the alias is still fully usable by the owner. - #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::put_alias_on_sale())] - pub fn put_alias_on_sale( - origin: OriginFor, - price: BalanceOf, - allowed_buyers: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(price > Zero::zero(), Error::::PriceCannotBeZero); - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - - // Refuse to re-list if already listed (must cancel first). - ensure!( - !AliasListings::::contains_key(&alias), - Error::::AlreadyForSale - ); - - // Convert Vec to BoundedVec (enforces MAX_WHITELIST_SIZE). - let bounded_buyers: Option< - BoundedVec>, - > = match allowed_buyers { - None => None, - Some(v) => Some(v.try_into().map_err(|_| Error::::WhitelistTooLarge)?), - }; - - let private = bounded_buyers.is_some(); - let listing = SaleListing { - price, - allowed_buyers: bounded_buyers, - }; - - AliasListings::::insert(&alias, listing); - - Self::deposit_event(Event::AliasListedForSale { - seller: who, - alias, - price, - private, - }); - Ok(()) - } - - /// Cancel a sale listing, keeping the alias. - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::cancel_sale())] - pub fn cancel_sale(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - - ensure!( - AliasListings::::contains_key(&alias), - Error::::NotForSale - ); - - AliasListings::::remove(&alias); - - Self::deposit_event(Event::AliasSaleCancelled { seller: who, alias }); - Ok(()) - } - - /// Purchase a listed alias. - /// - /// The buyer must: - /// - Not already own an alias. - /// - Have enough free balance for (price + AliasDeposit). - /// - /// Atomically: - /// 1. Payment (`price` ORB) transferred from buyer to seller. - /// 2. Seller's deposit unreserved back to seller. - /// 3. Buyer's deposit reserved from buyer. - /// 4. Alias ownership transferred. - #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::buy_alias())] - pub fn buy_alias(origin: OriginFor, alias: AliasOf) -> DispatchResult { - let buyer = ensure_signed(origin)?; - - // Buyer must not already own an alias. - ensure!( - !AccountAliases::::contains_key(&buyer), - Error::::BuyerAlreadyHasAlias - ); - - // Retrieve the sale listing. - let listing = AliasListings::::get(&alias).ok_or(Error::::NotForSale)?; - - // Whitelist check: if the listing is private, buyer must be in the list. - if let Some(ref whitelist) = listing.allowed_buyers { - ensure!(whitelist.contains(&buyer), Error::::NotInWhitelist); - } - - // Load the identity record to get the seller and their deposit. - let mut record = Identities::::get(&alias).ok_or(Error::::NoAlias)?; - let seller = record.owner.clone(); - - // Step 1: transfer sale price from buyer to seller (free → free). - T::Currency::transfer( - &buyer, - &seller, - listing.price, - ExistenceRequirement::KeepAlive, - )?; - - // Step 2: unreserve seller's deposit back to seller. - T::Currency::unreserve(&seller, record.deposit); - - // Step 3: reserve buyer's deposit (buyer now owns the alias). - T::Currency::reserve(&buyer, record.deposit)?; - - // Step 4: update storage — atomic at block commit. - AliasListings::::remove(&alias); - AccountAliases::::remove(&seller); - let new_evm = T::AccountIdToEvmAddress::convert(buyer.clone()); - record.owner = buyer.clone(); - record.evm_address = new_evm; - Identities::::insert(&alias, record); - AccountAliases::::insert(&buyer, &alias); - - Self::deposit_event(Event::AliasSold { - seller, - buyer, - alias, - price: listing.price, - }); - Ok(()) - } - - /// Add a link to an external blockchain account by verifying a signature. - /// - /// Supported chains (Initial Phase): - /// - ChainId 1 (Ethereum): Verifies an EIP-191 style signature of the Substrate AccountId. - #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::add_chain_link())] - pub fn add_chain_link( - origin: OriginFor, - chain_id: ChainId, - address: Vec, - signature: Vec, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - // Account must have an alias to manage identity. - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - let mut record = Identities::::get(&alias).ok_or(Error::::NoAlias)?; - - // Check if already exists. - ensure!( - !record.chain_links.iter().any(|l| l.chain_id == chain_id), - Error::::ChainLinkAlreadyExists - ); - - // [H-2 FIX] Single ECDSA recovery via utils (no duplication). - // [M-3 FIX] Unknown chain_id returns UnsupportedChain, not InvalidSignature. - let scheme = SupportedChains::::get(chain_id).ok_or(Error::::UnsupportedChain)?; - - match scheme { - SignatureScheme::Eip191 => { - // Ethereum: EIP-191 — mensaje = prefix32 + keccak256(AccountId32). - let sig_array: [u8; 65] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ethereum_signature( - address.as_slice(), - &who.encode(), - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - SignatureScheme::Ed25519 => { - // Solana/Ed25519 signature of the AccountId32. - let sig_array: [u8; 64] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ed25519_signature( - address.as_slice(), - &who.encode(), - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - } - - // Add the link. - let bounded_addr: ExternalAddr = address - .clone() - .try_into() - .map_err(|_| Error::::InvalidAliasCharacters)?; // Max length exceeded - - record - .chain_links - .try_push(ChainLink { - chain_id, - address: bounded_addr.clone(), - }) - .map_err(|_| Error::::TooManyLinks)?; - - Identities::::insert(&alias, record); - ReverseChainLinks::::insert((chain_id, bounded_addr.clone()), &who); - - Self::deposit_event(Event::ChainLinkAdded { - account: who, - chain_id, - address: bounded_addr, - }); - Ok(()) - } - - /// Remove an existing chain link. - #[pallet::call_index(9)] - #[pallet::weight(T::WeightInfo::remove_chain_link())] - pub fn remove_chain_link(origin: OriginFor, chain_id: ChainId) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - let mut record = Identities::::get(&alias).ok_or(Error::::NoAlias)?; - - let pos = record - .chain_links - .iter() - .position(|l| l.chain_id == chain_id) - .ok_or(Error::::ChainLinkNotFound)?; - - let link = record.chain_links.remove(pos); - Identities::::insert(&alias, record); - ReverseChainLinks::::remove((chain_id, link.address)); - - Self::deposit_event(Event::ChainLinkRemoved { - account: who, - chain_id, - }); - Ok(()) - } - - /// Update account profile metadata. - #[pallet::call_index(10)] - #[pallet::weight(T::WeightInfo::set_account_metadata())] - pub fn set_account_metadata( - origin: OriginFor, - display_name: Option>>, - bio: Option>>, - avatar: Option>>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - // [H-3 FIX] Only accounts that already paid the alias deposit can - // write metadata, preventing mass spam in the state trie. - ensure!(AccountAliases::::contains_key(&who), Error::::NoAlias); - - let metadata = AccountMetadata { - display_name, - bio, - avatar, - }; - AccountMetadatas::::insert(&who, metadata); - - Self::deposit_event(Event::MetadataUpdated { account: who }); - Ok(()) - } - - /// Dispatch a call on behalf of a linked external account. - /// - /// This is the "Universal Proxy Signing" feature. It allows a wallet from another - /// chain (e.g. Solana via Phantom) to authorize an action on its Orbinum identity. - #[pallet::call_index(13)] - #[pallet::weight(T::WeightInfo::dispatch_as_linked_account())] - pub fn dispatch_as_linked_account( - origin: OriginFor, - owner: T::AccountId, - chain_id: ChainId, - address: Vec, - signature: Vec, - call: Box<::RuntimeCall>, - ) -> DispatchResult { - let _relayer = ensure_signed(origin)?; // Anyone can pay gas to proxy the call - - // Guard: reject calls targeting the native EVM chain. - // Secp256k1 accounts can sign Substrate extrinsics directly via - // OrbinumSignature — no delegated dispatch needed for native EVM links. - ensure!( - chain_id != T::NativeEvmChainId::get(), - Error::::UseNativeSignatureForEvmAccounts - ); - - // 1. Verify that (chain_id, address) is linked to 'owner'. - let bounded_addr: ExternalAddr = address - .clone() - .try_into() - .map_err(|_| Error::::InvalidAliasCharacters)?; - let actual_owner = ReverseChainLinks::::get((chain_id, bounded_addr.clone())) - .ok_or(Error::::NotOwnerOfLink)?; - ensure!(actual_owner == owner, Error::::NotOwnerOfLink); - - // 2. Verify signature of the call payload. - // Payload must be the encoded Call. - let payload = call.encode(); - let scheme = SupportedChains::::get(chain_id).ok_or(Error::::UnsupportedChain)?; - - match scheme { - SignatureScheme::Eip191 => { - let sig_array: [u8; 65] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ethereum_signature( - address.as_slice(), - &payload, - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - SignatureScheme::Ed25519 => { - let sig_array: [u8; 64] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ed25519_signature( - address.as_slice(), - &payload, - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - } - - // 3. Dispatch the call with the owner's origin. - let result = - call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(owner.clone()).into()); - - let dispatch_result = result.map(|_| ()).map_err(|e| e.error); - - dispatch_result?; - - Self::deposit_event(Event::ProxyCallExecuted { - owner, - chain_id, - address: bounded_addr, - }); - Ok(()) - } - - /// Register a private chain link by storing only its commitment on-chain. - /// - /// The real address is never written to chain. The commitment must be computed - /// off-chain using Poseidon: - /// `inner = Poseidon2(Fr::from(chain_id), address_as_field_element)` - /// `commitment = Poseidon2(inner, blinding_as_field_element)` - /// - /// Encoding: chain_id → `Fr::from(u64)`, address → zero-padded to 32 bytes then LE - /// field element, blinding → 32-byte scalar as LE field element. - /// - /// - `chain_id` does NOT need to be in `SupportedChains` (no signature check here). - /// - Private links coexist with public links under the same alias. - /// - Maximum `MAX_CHAIN_LINKS` private links per alias (shared limit). - #[pallet::call_index(14)] - #[pallet::weight(T::WeightInfo::register_private_link())] - pub fn register_private_link( - origin: OriginFor, - chain_id: ChainId, - commitment: [u8; 32], - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - let mut links = PrivateChainLinks::::get(&alias); - - // Reject duplicate chain_id or identical commitment. - ensure!( - !links - .iter() - .any(|l| l.chain_id == chain_id || l.commitment == commitment), - Error::::PrivateLinkAlreadyExists - ); - - links - .try_push(PrivateChainLink { - chain_id, - commitment, - }) - .map_err(|_| Error::::TooManyPrivateLinks)?; - - PrivateChainLinks::::insert(&alias, links); - Self::deposit_event(Event::PrivateChainLinkAdded { - account: who, - chain_id, - commitment, - }); - Ok(()) - } - - /// Remove a private chain link by its commitment. - /// - /// No signature required: owning the alias is sufficient proof. - /// The address is never revealed during removal. - #[pallet::call_index(15)] - #[pallet::weight(T::WeightInfo::remove_private_link())] - pub fn remove_private_link(origin: OriginFor, commitment: [u8; 32]) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - let mut links = PrivateChainLinks::::get(&alias); - - let pos = links - .iter() - .position(|l| l.commitment == commitment) - .ok_or(Error::::PrivateLinkNotFound)?; - - let removed = links.remove(pos); - PrivateChainLinks::::insert(&alias, links); - - Self::deposit_event(Event::PrivateChainLinkRemoved { - account: who, - chain_id: removed.chain_id, - commitment, - }); - Ok(()) - } - - /// Reveal a private link and promote it permanently to a public chain link. - /// - /// Steps: - /// 1. Verifies `Poseidon2(Poseidon2(chain_id_fe, address_fe), blinding_fe) == stored commitment`. - /// 2. Verifies the external wallet signature of the owner's AccountId - /// (same verification as `add_chain_link`). - /// 3. Removes the commitment from `PrivateChainLinks`. - /// 4. Inserts the address into the public `chain_links` of the `IdentityRecord`. - /// - /// **This operation is irreversible.** Once revealed, the link is public. - /// - /// Use case: controlled revelation — proving wallet ownership to an auditor, - /// regulator, DAO, or grant committee without having been public from the start. - #[pallet::call_index(16)] - #[pallet::weight(T::WeightInfo::reveal_private_link())] - pub fn reveal_private_link( - origin: OriginFor, - commitment: [u8; 32], - address: Vec, - blinding: [u8; 32], - signature: Vec, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let alias = AccountAliases::::get(&who).ok_or(Error::::NoAlias)?; - let mut private_links = PrivateChainLinks::::get(&alias); - - // 1. Locate and stage removal of the private link. - let pos = private_links - .iter() - .position(|l| l.commitment == commitment) - .ok_or(Error::::PrivateLinkNotFound)?; - let link = private_links.remove(pos); - let chain_id = link.chain_id; - - // 2. Verify commitment: Poseidon2(Poseidon2(chain_id_fe, address_fe), blinding_fe) - // Encoding: - // chain_id_fe : Fr::from(chain_id as u64) → 4-byte LE, zero-padded to 32 - // address_fe : address bytes, zero-padded on right to 32 bytes, as LE field element - // blinding_fe : 32-byte random scalar, as LE field element - let mut chain_id_fe_bytes = [0u8; 32]; - chain_id_fe_bytes[..4].copy_from_slice(&chain_id.to_le_bytes()); - - let mut addr_fe_bytes = [0u8; 32]; - let copy_len = address.len().min(32); - addr_fe_bytes[..copy_len].copy_from_slice(&address[..copy_len]); - - let inner = orbinum_zk_core::host_interface::poseidon_host_interface::poseidon_hash_2( - &chain_id_fe_bytes, - &addr_fe_bytes, - ); - let computed_vec = - orbinum_zk_core::host_interface::poseidon_host_interface::poseidon_hash_2( - &inner, &blinding, - ); - let computed: [u8; 32] = computed_vec - .try_into() - .map_err(|_| Error::::CommitmentMismatch)?; - ensure!(computed == commitment, Error::::CommitmentMismatch); - - // 3. Verify the external wallet signature of the owner's AccountId. - let scheme = SupportedChains::::get(chain_id).ok_or(Error::::UnsupportedChain)?; - match scheme { - SignatureScheme::Eip191 => { - let sig_array: [u8; 65] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ethereum_signature( - address.as_slice(), - &who.encode(), - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - SignatureScheme::Ed25519 => { - let sig_array: [u8; 64] = signature - .try_into() - .map_err(|_| Error::::InvalidSignature)?; - crate::utils::verify_ed25519_signature( - address.as_slice(), - &who.encode(), - &sig_array, - ) - .map_err(|_| Error::::InvalidSignature)?; - } - } - - // 4. Promote to public chain link. - let mut record = Identities::::get(&alias).ok_or(Error::::NoAlias)?; - let bounded_addr: ExternalAddr = address - .clone() - .try_into() - .map_err(|_| Error::::InvalidAliasCharacters)?; - - ensure!( - !record.chain_links.iter().any(|l| l.chain_id == chain_id), - Error::::ChainLinkAlreadyExists - ); - - record - .chain_links - .try_push(ChainLink { - chain_id, - address: bounded_addr.clone(), - }) - .map_err(|_| Error::::TooManyLinks)?; - - // 5. Persist all changes atomically. - PrivateChainLinks::::insert(&alias, private_links); - Identities::::insert(&alias, record); - ReverseChainLinks::::insert((chain_id, bounded_addr.clone()), &who); - - Self::deposit_event(Event::PrivateChainLinkRevealed { - account: who, - chain_id, - address: bounded_addr, - }); - Ok(()) - } - - /// Dispatch a call using a privately linked wallet without revealing its address. - /// - /// This is the Phase 2 completion of private identity. The caller provides: - /// - `owner`: the Orbinum account that owns the alias and the private link. - /// - `commitment`: the `[u8; 32]` stored in `PrivateChainLinks`. - /// - `zk_proof`: a Groth16 proof asserting both: - /// (a) Knowledge of `(address, blinding)` matching the commitment. - /// (b) The external wallet at `address` signed `call_hash`. - /// - `call`: the Substrate call to execute as `owner`. - /// - /// The wallet address is **never revealed** at any point. - /// - /// Gas can be paid by a relayer (any signed origin). - /// - /// **Requires**: the `private_link_dispatch` ZK circuit artifact in - /// `pallet-zk-verifier`. Set `type PrivateLinkVerifier = DisabledPrivateLinkVerifier` - /// until the circuit is deployed. - #[pallet::call_index(17)] - #[pallet::weight(T::WeightInfo::dispatch_as_private_link())] - pub fn dispatch_as_private_link( - origin: OriginFor, - owner: T::AccountId, - commitment: [u8; 32], - zk_proof: Vec, - call: Box<::RuntimeCall>, - ) -> DispatchResult { - let _relayer = ensure_signed(origin)?; - - // 1. Verify alias exists for owner. - let alias = AccountAliases::::get(&owner).ok_or(Error::::NoAlias)?; - - // 2. Verify the commitment is a registered private link. - let links = PrivateChainLinks::::get(&alias); - ensure!( - links.iter().any(|l| l.commitment == commitment), - Error::::PrivateLinkNotFound - ); - - // 3. Compute call_hash = blake2_256(encoded call) — this is the - // signed message verified inside the ZK circuit. - let call_encoded = call.encode(); - let call_hash = sp_io::hashing::blake2_256(&call_encoded); - - // 4. Verify the ZK proof via the configured verifier port. - ensure!( - T::PrivateLinkVerifier::verify(&commitment, &call_hash, &zk_proof), - Error::::InvalidProof - ); - - // 5. Dispatch the call with the owner's origin. - call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(owner.clone()).into()) - .map(|_| ()) - .map_err(|e| e.error)?; - - Self::deposit_event(Event::PrivateLinkDispatchExecuted { owner, commitment }); - Ok(()) - } - } // end #[pallet::call] - - // ────────────────────────────────────────────── - // Helper impls - // ────────────────────────────────────────────── - - impl Pallet { - /// Resolve an alias string to its IdentityRecord. - /// - /// Returns `None` if the alias has an invalid format or is not registered. - /// [L-2 FIX] Validates characters and length before querying storage. - pub fn resolve_alias(alias: &[u8]) -> Option> { - crate::utils::validate_alias(alias).ok()?; - let bounded: AliasOf = alias.to_vec().try_into().ok()?; - Identities::::get(&bounded) - } - - pub fn get_account_metadata(account: &T::AccountId) -> Option { - AccountMetadatas::::get(account) - } - - /// Returns all supported chains and their schemes. - pub fn get_supported_chains() -> Vec<(u32, SignatureScheme)> { - SupportedChains::::iter().collect() - } - } -} diff --git a/frame/account-mapping/src/mock.rs b/frame/account-mapping/src/mock.rs deleted file mode 100644 index 5c0b97da..00000000 --- a/frame/account-mapping/src/mock.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::{self as pallet_account_mapping, PrivateLinkVerifierPort}; -use frame_support::{derive_impl, parameter_types}; -use sp_core::H160; -use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; -use sp_runtime::{ - traits::{BlakeTwo256, Convert, IdentityLookup}, - BuildStorage, -}; - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test { - System: frame_system, - Balances: pallet_balances, - AccountMapping: pallet_account_mapping, - } -); - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type Block = Block; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type RuntimeTask = RuntimeTask; - type Hash = sp_core::H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Nonce = u64; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type DbWeight = frame_support::weights::constants::RocksDbWeight; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type AccountStore = System; -} - -pub struct TestEvmAddress; -impl Convert> for TestEvmAddress { - fn convert(account: u64) -> Option { - if account == 42 { - None - } else { - Some(H160::from_low_u64_be(account % 2)) - } - } -} - -parameter_types! { - pub const TestAliasDeposit: u64 = 100; - pub const TestMaxAliasLength: u32 = 32; - /// Simulated native EVM chain ID for tests. - pub const TestNativeEvmChainId: u32 = 9999; -} - -impl pallet_account_mapping::Config for Test { - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type AccountIdToEvmAddress = TestEvmAddress; - type AliasDeposit = TestAliasDeposit; - type MaxAliasLength = TestMaxAliasLength; - type NativeEvmChainId = TestNativeEvmChainId; - type WeightInfo = pallet_account_mapping::weights::SubstrateWeight; - type PrivateLinkVerifier = MockPrivateLinkVerifier; - - /// Account 100 simulates a secp256k1 OrbinumSignature account in tests: - /// its H160 is always derivable implicitly — no storage entry needed. - fn is_implicit_evm_account(account: &u64) -> bool { - *account == 100 - } -} - -pub struct MockPrivateLinkVerifier; -impl PrivateLinkVerifierPort for MockPrivateLinkVerifier { - fn verify(_commitment: &[u8; 32], _call_hash: &[u8; 32], proof: &[u8]) -> bool { - proof.first().copied() == Some(0x01) - } -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![ - (1, 10_000), - (2, 10_000), - (3, 10_000), - (42, 10_000), - (100, 10_000), - ], - dev_accounts: None, - } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); - ext.execute_with(|| System::set_block_number(1)); - ext -} diff --git a/frame/account-mapping/src/ports.rs b/frame/account-mapping/src/ports.rs deleted file mode 100644 index 46de352c..00000000 --- a/frame/account-mapping/src/ports.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub trait PrivateLinkVerifierPort { - fn verify(commitment: &[u8; 32], call_hash: &[u8; 32], proof: &[u8]) -> bool; -} - -pub struct DisabledPrivateLinkVerifier; - -impl PrivateLinkVerifierPort for DisabledPrivateLinkVerifier { - fn verify(_commitment: &[u8; 32], _call_hash: &[u8; 32], _proof: &[u8]) -> bool { - false - } -} diff --git a/frame/account-mapping/src/runtime_api_impl.rs b/frame/account-mapping/src/runtime_api_impl.rs deleted file mode 100644 index 983d9602..00000000 --- a/frame/account-mapping/src/runtime_api_impl.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::pallet::{ - AccountAliases, AliasListings, Identities, PrivateChainLinks, ReverseChainLinks, -}; -use crate::{Config, Pallet}; -use alloc::vec::Vec; - -pub struct RawPrivateLink { - pub chain_id: u32, - pub commitment: [u8; 32], -} - -impl Pallet { - pub fn runtime_api_resolve_alias(alias: &[u8]) -> Option> { - Self::resolve_alias(alias) - } - - pub fn runtime_api_get_alias_of(account: T::AccountId) -> Option> { - AccountAliases::::get(account).map(|a| a.into_inner()) - } - - pub fn runtime_api_can_buy(alias: Vec, buyer: T::AccountId) -> bool { - if AccountAliases::::contains_key(&buyer) { - return false; - } - let Ok(()) = crate::utils::validate_alias(&alias) else { - return false; - }; - let Ok(bounded) = crate::pallet::AliasOf::::try_from(alias) else { - return false; - }; - let Some(listing) = AliasListings::::get(&bounded) else { - return false; - }; - if let Some(whitelist) = &listing.allowed_buyers { - whitelist.contains(&buyer) - } else { - true - } - } - - pub fn runtime_api_get_link_owner(chain_id: u32, address: Vec) -> Option { - let bounded: crate::ExternalAddr = address.try_into().ok()?; - ReverseChainLinks::::get((chain_id, bounded)) - } - - pub fn runtime_api_get_private_links(alias: Vec) -> Option> { - crate::utils::validate_alias(&alias).ok()?; - let bounded: crate::pallet::AliasOf = alias.try_into().ok()?; - Identities::::contains_key(&bounded).then(|| { - PrivateChainLinks::::get(&bounded) - .iter() - .map(|l| RawPrivateLink { - chain_id: l.chain_id, - commitment: l.commitment, - }) - .collect() - }) - } - - pub fn runtime_api_has_private_link(alias: Vec, commitment: [u8; 32]) -> bool { - let Ok(()) = crate::utils::validate_alias(&alias) else { - return false; - }; - let Ok(bounded) = crate::pallet::AliasOf::::try_from(alias) else { - return false; - }; - PrivateChainLinks::::get(&bounded) - .iter() - .any(|l| l.commitment == commitment) - } -} diff --git a/frame/account-mapping/src/tests/alias.rs b/frame/account-mapping/src/tests/alias.rs deleted file mode 100644 index b4da38cd..00000000 --- a/frame/account-mapping/src/tests/alias.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, Balances, RuntimeOrigin, Test}; -use crate::Error; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn register_alias_succeeds_and_reserves_deposit() { - new_test_ext().execute_with(|| { - let alias = b"nolasco".to_vec().try_into().unwrap(); - let before = Balances::free_balance(1); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_eq!(Balances::reserved_balance(1), 100); - assert_eq!(Balances::free_balance(1), before - 100); - }); -} - -#[test] -fn register_alias_creates_bidirectional_index() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"nolasco".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let record = AccountMapping::identity_of(alias.clone()).expect("identity must exist"); - assert_eq!(record.owner, 1); - - let stored_alias = AccountMapping::alias_of(1).expect("alias index must exist"); - assert_eq!(stored_alias, alias); - }); -} - -#[test] -fn register_alias_fails_if_already_has_alias() { - new_test_ext().execute_with(|| { - let alias1 = b"first".to_vec().try_into().unwrap(); - let alias2 = b"second".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias1 - )); - assert_noop!( - AccountMapping::register_alias(RuntimeOrigin::signed(1), alias2), - Error::::AlreadyHasAlias - ); - }); -} - -#[test] -fn register_alias_fails_if_alias_taken() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"taken".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - assert_noop!( - AccountMapping::register_alias(RuntimeOrigin::signed(2), alias), - Error::::AliasTaken - ); - }); -} - -#[test] -fn register_alias_rejects_invalid_characters() { - new_test_ext().execute_with(|| { - let bad: crate::pallet::AliasOf = b"Bad@Alias!".to_vec().try_into().unwrap(); - assert_noop!( - AccountMapping::register_alias(RuntimeOrigin::signed(1), bad), - Error::::InvalidAliasCharacters - ); - }); -} - -#[test] -fn register_alias_rejects_too_short_alias() { - new_test_ext().execute_with(|| { - let short: crate::pallet::AliasOf = b"ab".to_vec().try_into().unwrap(); - assert_noop!( - AccountMapping::register_alias(RuntimeOrigin::signed(1), short), - Error::::AliasTooShort - ); - }); -} - -#[test] -fn release_alias_unreserves_deposit_and_clears_index() { - new_test_ext().execute_with(|| { - let alias = b"nolasco".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - let before_release = Balances::free_balance(1); - assert_ok!(AccountMapping::release_alias(RuntimeOrigin::signed(1))); - - assert_eq!(Balances::free_balance(1), before_release + 100); - assert_eq!(Balances::reserved_balance(1), 0); - assert!(AccountMapping::alias_of(1).is_none()); - }); -} - -#[test] -fn release_alias_fails_if_no_alias() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::release_alias(RuntimeOrigin::signed(42)), - Error::::NoAlias - ); - }); -} - -#[test] -fn resolve_alias_returns_identity_record() { - new_test_ext().execute_with(|| { - let alias = b"queried".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(2), - alias - )); - - let record = crate::Pallet::::resolve_alias(b"queried").unwrap(); - assert_eq!(record.owner, 2); - assert!(record.chain_links.is_empty()); - }); -} - -#[test] -fn transfer_alias_moves_ownership_and_deposit() { - new_test_ext().execute_with(|| { - let alias = b"xfer_ok".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - let deposit_before_2 = Balances::reserved_balance(2); - - assert_ok!(AccountMapping::transfer_alias(RuntimeOrigin::signed(1), 2)); - - assert!(AccountMapping::alias_of(1).is_none()); - assert_eq!(Balances::reserved_balance(1), 0); - - let alias2 = AccountMapping::alias_of(2).expect("account 2 must now own the alias"); - assert_eq!(&alias2[..], b"xfer_ok"); - assert_eq!(Balances::reserved_balance(2), deposit_before_2 + 100); - - let record = AccountMapping::identity_of(alias2).expect("identity must exist"); - assert_eq!(record.owner, 2); - }); -} - -#[test] -fn transfer_alias_fails_if_caller_has_no_alias() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::transfer_alias(RuntimeOrigin::signed(42), 1), - Error::::NoAlias - ); - }); -} - -#[test] -fn transfer_alias_fails_if_destination_already_has_alias() { - new_test_ext().execute_with(|| { - let a1: crate::pallet::AliasOf = b"sender".to_vec().try_into().unwrap(); - let a2: crate::pallet::AliasOf = b"recvr".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias(RuntimeOrigin::signed(1), a1)); - assert_ok!(AccountMapping::register_alias(RuntimeOrigin::signed(2), a2)); - - assert_noop!( - AccountMapping::transfer_alias(RuntimeOrigin::signed(1), 2), - Error::::NewOwnerAlreadyHasAlias - ); - }); -} - -#[test] -fn transfer_alias_fails_if_self_transfer() { - new_test_ext().execute_with(|| { - let alias = b"selfxfr".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_noop!( - AccountMapping::transfer_alias(RuntimeOrigin::signed(1), 1), - Error::::CannotTransferToSelf - ); - }); -} - -#[test] -fn set_account_metadata_succeeds() { - new_test_ext().execute_with(|| { - let alias = b"nolasco".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - let name = Some(b"Nolasco".to_vec().try_into().unwrap()); - let bio = Some(b"Dev at Orbinum".to_vec().try_into().unwrap()); - - assert_ok!(AccountMapping::set_account_metadata( - RuntimeOrigin::signed(1), - name.clone(), - bio.clone(), - None, - )); - - let meta = AccountMapping::account_metadata(1).unwrap(); - assert_eq!(meta.display_name, name); - assert_eq!(meta.bio, bio); - assert_eq!(meta.avatar, None); - }); -} diff --git a/frame/account-mapping/src/tests/alias_sale.rs b/frame/account-mapping/src/tests/alias_sale.rs deleted file mode 100644 index 079b9744..00000000 --- a/frame/account-mapping/src/tests/alias_sale.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, Balances, RuntimeOrigin, Test}; -use crate::Error; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn buy_alias_full_flow() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"buyable".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - assert_ok!(AccountMapping::put_alias_on_sale( - RuntimeOrigin::signed(1), - 200_u64, - None - )); - - let seller_free_before = Balances::free_balance(1); - let buyer_free_before = Balances::free_balance(2); - - assert_ok!(AccountMapping::buy_alias( - RuntimeOrigin::signed(2), - alias.clone() - )); - assert_eq!(AccountMapping::alias_of(2), Some(alias.clone())); - assert!(AccountMapping::alias_of(1).is_none()); - assert_eq!(Balances::free_balance(1), seller_free_before + 200 + 100); - assert_eq!(Balances::reserved_balance(2), 100); - assert_eq!(Balances::free_balance(2), buyer_free_before - 200 - 100); - assert!(AccountMapping::listing_price(alias).is_none()); - }); -} - -#[test] -fn release_alias_clears_active_listing() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"forsale".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - assert_ok!(AccountMapping::put_alias_on_sale( - RuntimeOrigin::signed(1), - 500_u64, - None - )); - assert!(AccountMapping::listing_price(alias.clone()).is_some()); - - assert_ok!(AccountMapping::release_alias(RuntimeOrigin::signed(1))); - - assert!( - AccountMapping::listing_price(alias).is_none(), - "[C-1] El listing no fue eliminado al liberar el alias" - ); - }); -} - -#[test] -fn transfer_alias_clears_active_listing() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"txfrsale".to_vec().try_into().unwrap(); - - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - assert_ok!(AccountMapping::put_alias_on_sale( - RuntimeOrigin::signed(1), - 500_u64, - None - )); - assert!(AccountMapping::listing_price(alias.clone()).is_some()); - - assert_ok!(AccountMapping::transfer_alias(RuntimeOrigin::signed(1), 2)); - - assert!( - AccountMapping::listing_price(alias).is_none(), - "[C-2] El listing fue heredado por el nuevo propietario sin su consentimiento" - ); - }); -} - -#[test] -fn set_account_metadata_requires_alias() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::set_account_metadata(RuntimeOrigin::signed(3), None, None, None), - Error::::NoAlias - ); - }); -} diff --git a/frame/account-mapping/src/tests/chain_links.rs b/frame/account-mapping/src/tests/chain_links.rs deleted file mode 100644 index 139c742f..00000000 --- a/frame/account-mapping/src/tests/chain_links.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, RuntimeCall, RuntimeOrigin, Test}; -use crate::{Error, SignatureScheme}; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn remove_chain_link_succeeds() { - new_test_ext().execute_with(|| { - let alias_key: crate::pallet::AliasOf = b"multichain".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias_key.clone() - )); - - let mut record = crate::Identities::::get(&alias_key).unwrap(); - record - .chain_links - .try_push(crate::ChainLink { - chain_id: 1, - address: b"0x123".to_vec().try_into().unwrap(), - }) - .unwrap(); - crate::Identities::::insert(&alias_key, record); - - assert_ok!(AccountMapping::remove_chain_link( - RuntimeOrigin::signed(1), - 1 - )); - - let record_after = crate::Identities::::get(&alias_key).unwrap(); - assert!(record_after.chain_links.is_empty()); - }); -} - -#[test] -fn governance_can_add_and_remove_supported_chains() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::add_supported_chain( - RuntimeOrigin::root(), - 123, - SignatureScheme::Ed25519, - )); - assert_eq!( - AccountMapping::supported_chain(123), - Some(SignatureScheme::Ed25519) - ); - - assert_ok!(AccountMapping::remove_supported_chain( - RuntimeOrigin::root(), - 123 - )); - assert_eq!(AccountMapping::supported_chain(123), None); - }); -} - -#[test] -fn add_chain_link_fails_for_unsupported_chain() { - new_test_ext().execute_with(|| { - let alias = b"chaintest".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - let dummy_sig = [0u8; 65]; - let addr: crate::ExternalAddr = b"some_address".to_vec().try_into().unwrap(); - - assert_noop!( - AccountMapping::add_chain_link( - RuntimeOrigin::signed(1), - 9999, - addr.to_vec(), - dummy_sig.to_vec(), - ), - Error::::UnsupportedChain - ); - }); -} - -#[test] -fn add_chain_link_works_after_governance_registration() { - new_test_ext().execute_with(|| { - let alias = b"dynamic".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_ok!(AccountMapping::add_supported_chain( - RuntimeOrigin::root(), - 501, - SignatureScheme::Ed25519, - )); - - let addr: crate::ExternalAddr = [0u8; 32].to_vec().try_into().unwrap(); - let sig = [0u8; 64].to_vec(); - - assert_ok!(AccountMapping::add_chain_link( - RuntimeOrigin::signed(1), - 501, - addr.to_vec(), - sig - )); - }); -} - -// ── Phase 2 alignment: dispatch_as_linked_account guard for native EVM ─────── - -fn remark_call() -> alloc::boxed::Box { - alloc::boxed::Box::new(RuntimeCall::System(frame_system::Call::remark { - remark: alloc::vec![], - })) -} - -/// Calling dispatch_as_linked_account with the native EVM chain ID must fail -/// immediately — no signature or storage check should be reached. -#[test] -fn dispatch_as_linked_account_rejects_native_evm_chain_id() { - new_test_ext().execute_with(|| { - // TestNativeEvmChainId = 9999 in mock. - // The guard fires before any storage lookup, so no setup is needed. - assert_noop!( - AccountMapping::dispatch_as_linked_account( - RuntimeOrigin::signed(99), - 1u64, // owner (irrelevant — rejected before lookup) - 9999, // chain_id == TestNativeEvmChainId - b"0xdeadbeef".to_vec(), - [0u8; 65].to_vec(), - remark_call(), - ), - Error::::UseNativeSignatureForEvmAccounts - ); - }); -} - -/// Calling dispatch_as_linked_account with a non-native chain ID must pass the -/// NativeEvmChainId guard and proceed to the storage lookup phase. -#[test] -fn dispatch_as_linked_account_accepts_non_evm_chain_id() { - new_test_ext().execute_with(|| { - // chain_id = 1 (Ethereum mainnet, NOT the native EVM chain 9999). - // No link is registered, so NotOwnerOfLink is the expected error — - // not UseNativeSignatureForEvmAccounts. - assert_noop!( - AccountMapping::dispatch_as_linked_account( - RuntimeOrigin::signed(99), - 1u64, - 1, // non-native chain id — guard should pass - b"0xdeadbeef".to_vec(), - [0u8; 65].to_vec(), - remark_call(), - ), - Error::::NotOwnerOfLink - ); - }); -} diff --git a/frame/account-mapping/src/tests/evm_mapping.rs b/frame/account-mapping/src/tests/evm_mapping.rs deleted file mode 100644 index 308186d2..00000000 --- a/frame/account-mapping/src/tests/evm_mapping.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, RuntimeOrigin, Test}; -use crate::{Error, MappedAccounts, OriginalAccounts}; -use frame_support::{assert_noop, assert_ok}; -use sp_core::H160; - -#[test] -fn native_account_cannot_be_mapped() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::map_account(RuntimeOrigin::signed(42)), - Error::::NativeAccountCannotBeMapped - ); - }); -} - -#[test] -fn map_account_stores_bidirectional_mapping() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(1))); - - let address = H160::from_low_u64_be(1); - assert_eq!(AccountMapping::mapped_account(address), Some(1)); - assert_eq!(AccountMapping::mapped_address(1), Some(address)); - }); -} - -#[test] -fn map_account_fails_if_account_already_mapped() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(2))); - assert_noop!( - AccountMapping::map_account(RuntimeOrigin::signed(2)), - Error::::AlreadyMapped - ); - }); -} - -#[test] -fn map_account_fails_if_address_already_mapped() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(1))); - assert_noop!( - AccountMapping::map_account(RuntimeOrigin::signed(3)), - Error::::AddressAlreadyMapped - ); - }); -} - -#[test] -fn unmap_account_removes_bidirectional_mapping() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(2))); - - let address = H160::from_low_u64_be(0); - assert_eq!(AccountMapping::mapped_account(address), Some(2)); - - assert_ok!(AccountMapping::unmap_account(RuntimeOrigin::signed(2))); - - assert_eq!(AccountMapping::mapped_account(address), None); - assert_eq!(AccountMapping::mapped_address(2), None); - }); -} - -#[test] -fn unmap_account_fails_if_not_mapped() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::unmap_account(RuntimeOrigin::signed(42)), - Error::::NotMapped - ); - }); -} - -#[test] -fn account_can_remap_after_unmap() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(1))); - assert_ok!(AccountMapping::unmap_account(RuntimeOrigin::signed(1))); - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(1))); - - let address = H160::from_low_u64_be(1); - assert_eq!(AccountMapping::mapped_account(address), Some(1)); - assert_eq!(AccountMapping::mapped_address(1), Some(address)); - }); -} - -// ── Phase 1 alignment: implicit secp256k1 accounts (account 100 in mock) ──── - -/// map_account for an implicit secp256k1 account must NOT write to storage. -/// The mapping is always derivable from the AccountId — storage is redundant. -#[test] -fn map_account_secp256k1_does_not_write_storage() { - new_test_ext().execute_with(|| { - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(100))); - - // No storage entry written — mapping is implicit. - assert!(!OriginalAccounts::::contains_key(100)); - assert_eq!(AccountMapping::mapped_address(100), None); - - // Calling again is idempotent (no AlreadyMapped error). - assert_ok!(AccountMapping::map_account(RuntimeOrigin::signed(100))); - }); -} - -/// unmap_account for an implicit secp256k1 account with no prior map_account -/// must succeed (emit event) without returning NotMapped. -#[test] -fn unmap_account_secp256k1_without_storage_entry_succeeds() { - new_test_ext().execute_with(|| { - // Never called map_account — no storage entry exists. - assert_ok!(AccountMapping::unmap_account(RuntimeOrigin::signed(100))); - - // Storage remains empty. - assert!(!OriginalAccounts::::contains_key(100)); - }); -} - -/// unmap_account for an implicit secp256k1 account that has a legacy storage -/// entry (written before Phase 3) must clean up the entry. -#[test] -fn unmap_account_secp256k1_cleans_up_legacy_storage_entry() { - new_test_ext().execute_with(|| { - // Simulate a legacy entry written directly (as if from before Phase 3). - let address = H160::from_low_u64_be(0); // TestEvmAddress::convert(100) = H160::from_low_u64_be(0) - OriginalAccounts::::insert(100u64, address); - MappedAccounts::::insert(address, 100u64); - - assert_ok!(AccountMapping::unmap_account(RuntimeOrigin::signed(100))); - - // Legacy entries removed. - assert!(!OriginalAccounts::::contains_key(100u64)); - assert_eq!(MappedAccounts::::get(address), None); - }); -} diff --git a/frame/account-mapping/src/tests/mod.rs b/frame/account-mapping/src/tests/mod.rs deleted file mode 100644 index c1a19a24..00000000 --- a/frame/account-mapping/src/tests/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod alias; -mod alias_sale; -mod chain_links; -mod evm_mapping; -mod private_dispatch; -mod private_links; - -pub(super) fn make_commitment(chain_id: u32, address: &[u8], blinding: &[u8; 32]) -> [u8; 32] { - use orbinum_zk_core::host_interface::poseidon_host_interface; - - let mut chain_id_bytes = [0u8; 32]; - chain_id_bytes[..4].copy_from_slice(&chain_id.to_le_bytes()); - - let mut addr_padded = [0u8; 32]; - let copy_len = address.len().min(32); - addr_padded[..copy_len].copy_from_slice(&address[..copy_len]); - - let inner = poseidon_host_interface::poseidon_hash_2(&chain_id_bytes, &addr_padded); - let computed = poseidon_host_interface::poseidon_hash_2(&inner, blinding); - computed - .try_into() - .expect("Poseidon output must be 32 bytes") -} diff --git a/frame/account-mapping/src/tests/private_dispatch.rs b/frame/account-mapping/src/tests/private_dispatch.rs deleted file mode 100644 index acbc6d46..00000000 --- a/frame/account-mapping/src/tests/private_dispatch.rs +++ /dev/null @@ -1,218 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, RuntimeCall, RuntimeEvent, RuntimeOrigin, Test}; -use crate::Error; -use frame_support::{assert_noop, assert_ok}; - -use super::make_commitment; - -fn valid_proof() -> alloc::vec::Vec { - let mut v = alloc::vec![0u8; 64]; - v[0] = 0x01; - v -} - -fn invalid_proof() -> alloc::vec::Vec { - alloc::vec![0x00u8; 64] -} - -fn remark_call() -> alloc::boxed::Box { - alloc::boxed::Box::new(RuntimeCall::System(frame_system::Call::remark { - remark: alloc::vec![], - })) -} - -#[test] -fn dispatch_as_private_link_requires_alias() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - [0xAAu8; 32], - valid_proof(), - remark_call(), - ), - Error::::NoAlias - ); - }); -} - -#[test] -fn dispatch_as_private_link_fails_if_commitment_not_registered() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_noop!( - AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - [0xBBu8; 32], - valid_proof(), - remark_call(), - ), - Error::::PrivateLinkNotFound - ); - }); -} - -#[test] -fn dispatch_as_private_link_rejects_invalid_proof() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - let commitment = make_commitment(501, b"sol_addr", &[0x01u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - assert_noop!( - AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - commitment, - invalid_proof(), - remark_call(), - ), - Error::::InvalidProof - ); - }); -} - -#[test] -fn dispatch_as_private_link_executes_call_with_valid_proof() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"sol_addr", &[0x01u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - assert_ok!(AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - commitment, - valid_proof(), - remark_call(), - )); - - assert_eq!(AccountMapping::private_chain_links(alias).len(), 1); - }); -} - -#[test] -fn dispatch_as_private_link_emits_event() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"sol_addr", &[0x01u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - assert_ok!(AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - commitment, - valid_proof(), - remark_call(), - )); - - let events = frame_system::Pallet::::events(); - let found = events.iter().any(|e| { - matches!( - &e.event, - RuntimeEvent::AccountMapping(crate::Event::PrivateLinkDispatchExecuted { owner, commitment: c }) - if *owner == 1u64 && *c == commitment - ) - }); - assert!(found, "PrivateLinkDispatchExecuted event not emitted"); - }); -} - -#[test] -fn dispatch_as_private_link_can_be_called_by_relayer() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"sol_addr", &[0x77u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - assert_ok!(AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(3), - 1u64, - commitment, - valid_proof(), - remark_call(), - )); - }); -} - -#[test] -fn dispatch_as_private_link_does_not_reveal_address() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let addr = b"sol_secret_address__"; - let blinding = [0x42u8; 32]; - let commitment = make_commitment(501, addr, &blinding); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - assert_ok!(AccountMapping::dispatch_as_private_link( - RuntimeOrigin::signed(99), - 1u64, - commitment, - valid_proof(), - remark_call(), - )); - - let bounded_addr: crate::ExternalAddr = addr.to_vec().try_into().unwrap(); - assert!( - AccountMapping::link_owner((501u32, bounded_addr)).is_none(), - "Address was leaked into public ReverseChainLinks" - ); - - let links = AccountMapping::private_chain_links(alias); - assert_eq!(links.len(), 1); - assert_eq!(links[0].commitment, commitment); - }); -} - -extern crate alloc; diff --git a/frame/account-mapping/src/tests/private_links.rs b/frame/account-mapping/src/tests/private_links.rs deleted file mode 100644 index ba53b86a..00000000 --- a/frame/account-mapping/src/tests/private_links.rs +++ /dev/null @@ -1,329 +0,0 @@ -use crate::mock::{new_test_ext, AccountMapping, RuntimeOrigin, Test}; -use crate::{Error, SignatureScheme}; -use frame_support::{assert_noop, assert_ok}; - -use super::make_commitment; - -#[test] -fn register_private_link_requires_alias() { - new_test_ext().execute_with(|| { - assert_noop!( - AccountMapping::register_private_link(RuntimeOrigin::signed(1), 1u32, [0xAAu8; 32]), - Error::::NoAlias - ); - }); -} - -#[test] -fn register_private_link_happy_path() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"solana_address_here_32_bytes____", &[0x11u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - - let stored = AccountMapping::private_chain_links(alias); - assert_eq!(stored.len(), 1); - assert_eq!(stored[0].chain_id, 501u32); - assert_eq!(stored[0].commitment, commitment); - }); -} - -#[test] -fn register_private_link_rejects_duplicate_commitment() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"addr", &[0x22u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - assert_noop!( - AccountMapping::register_private_link(RuntimeOrigin::signed(1), 501u32, commitment), - Error::::PrivateLinkAlreadyExists - ); - }); -} - -#[test] -fn register_private_link_rejects_duplicate_chain_id() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let c1 = make_commitment(501, b"addr_one", &[0x33u8; 32]); - let c2 = make_commitment(501, b"addr_two", &[0x44u8; 32]); // mismo chain_id, distinto commitment - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - c1 - )); - assert_noop!( - AccountMapping::register_private_link(RuntimeOrigin::signed(1), 501u32, c2), - Error::::PrivateLinkAlreadyExists - ); - }); -} - -#[test] -fn multiple_private_links_can_coexist() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let c_sol = make_commitment(501, b"sol_addr", &[0x01u8; 32]); - let c_btc = make_commitment(60, b"btc_addr", &[0x02u8; 32]); - let c_eth = make_commitment(1, b"eth_addr", &[0x03u8; 32]); - - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - c_sol - )); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 60u32, - c_btc - )); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 1u32, - c_eth - )); - - assert_eq!(AccountMapping::private_chain_links(alias).len(), 3); - }); -} - -#[test] -fn remove_private_link_happy_path() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let commitment = make_commitment(501, b"addr", &[0x55u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - commitment - )); - assert_eq!(AccountMapping::private_chain_links(alias.clone()).len(), 1); - - assert_ok!(AccountMapping::remove_private_link( - RuntimeOrigin::signed(1), - commitment - )); - assert_eq!(AccountMapping::private_chain_links(alias).len(), 0); - }); -} - -#[test] -fn remove_private_link_fails_if_not_found() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_noop!( - AccountMapping::remove_private_link(RuntimeOrigin::signed(1), [0xFFu8; 32]), - Error::::PrivateLinkNotFound - ); - }); -} - -#[test] -fn release_alias_clears_private_links() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let c1 = make_commitment(501, b"sol_addr", &[0xAAu8; 32]); - let c2 = make_commitment(60, b"btc_addr", &[0xBBu8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - c1 - )); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 60u32, - c2 - )); - assert_eq!(AccountMapping::private_chain_links(alias.clone()).len(), 2); - - assert_ok!(AccountMapping::release_alias(RuntimeOrigin::signed(1))); - assert_eq!(AccountMapping::private_chain_links(alias).len(), 0); - }); -} - -#[test] -fn reveal_private_link_fails_if_commitment_not_found() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias - )); - - assert_noop!( - AccountMapping::reveal_private_link( - RuntimeOrigin::signed(1), - [0xFFu8; 32], - b"some_addr".to_vec(), - [0x00u8; 32], - vec![0u8; 65], - ), - Error::::PrivateLinkNotFound - ); - }); -} - -#[test] -fn reveal_private_link_fails_on_commitment_mismatch() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let addr = b"some_eth_address____"; - let blinding = [0xBEu8; 32]; - let real_commitment = make_commitment(1, addr, &blinding); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 1u32, - real_commitment - )); - - assert_noop!( - AccountMapping::reveal_private_link( - RuntimeOrigin::signed(1), - real_commitment, - addr.to_vec(), - [0xDEu8; 32], - vec![0u8; 65], - ), - Error::::CommitmentMismatch - ); - }); -} - -#[test] -fn reveal_private_link_fails_after_correct_commitment_if_chain_unsupported() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let addr = b"some_eth_address____"; - let blinding = [0xCAu8; 32]; - let chain_id = 9999u32; - let commitment = make_commitment(chain_id, addr, &blinding); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - chain_id, - commitment - )); - - assert_noop!( - AccountMapping::reveal_private_link( - RuntimeOrigin::signed(1), - commitment, - addr.to_vec(), - blinding, - vec![0u8; 65], - ), - Error::::UnsupportedChain - ); - }); -} - -#[test] -fn reveal_private_link_fails_with_invalid_signature_for_supported_chain() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - assert_ok!(AccountMapping::add_supported_chain( - RuntimeOrigin::root(), - 1u32, - SignatureScheme::Eip191, - )); - - let addr = b"some_eth_address____"; - let blinding = [0xFAu8; 32]; - let commitment = make_commitment(1, addr, &blinding); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 1u32, - commitment - )); - - assert_noop!( - AccountMapping::reveal_private_link( - RuntimeOrigin::signed(1), - commitment, - addr.to_vec(), - blinding, - vec![0u8; 65], - ), - Error::::InvalidSignature - ); - }); -} - -#[test] -fn private_and_public_links_coexist_on_same_alias() { - new_test_ext().execute_with(|| { - let alias: crate::pallet::AliasOf = b"alice".to_vec().try_into().unwrap(); - assert_ok!(AccountMapping::register_alias( - RuntimeOrigin::signed(1), - alias.clone() - )); - - let c = make_commitment(501, b"sol_addr", &[0x01u8; 32]); - assert_ok!(AccountMapping::register_private_link( - RuntimeOrigin::signed(1), - 501u32, - c - )); - - let record = AccountMapping::identity_of(alias.clone()).unwrap(); - assert_eq!(record.chain_links.len(), 0); - assert_eq!(AccountMapping::private_chain_links(alias).len(), 1); - }); -} diff --git a/frame/account-mapping/src/types.rs b/frame/account-mapping/src/types.rs deleted file mode 100644 index f59417aa..00000000 --- a/frame/account-mapping/src/types.rs +++ /dev/null @@ -1,121 +0,0 @@ -use frame_support::pallet_prelude::*; - -/// Maximum number of external links (public or private) per identity. -pub const MAX_CHAIN_LINKS: u32 = 16; - -/// Maximum length in bytes of an external address (covers BTC bech32, etc.). -pub const MAX_EXTERNAL_ADDR_LEN: u32 = 128; - -/// Maximum number of buyers in a private sale whitelist. -pub const MAX_WHITELIST_SIZE: u32 = 20; - -/// Maximum length for metadata strings (bio, avatar CID, etc.). -pub const MAX_METADATA_LEN: u32 = 128; - -/// Opaque chain identifier used as storage key for cross-chain links. -/// -/// The `u32` namespace is split into two ranges using the high bit (bit 31): -/// -/// | Bit 31 | Range | Standard | Examples | -/// |--------|-------|----------|---------| -/// | `0` | `0x0000_0000 – 0x7FFF_FFFF` | EIP-155 (EVM chain IDs) | Ethereum = 1, Polygon = 137 | -/// | `1` | `0x8000_0000 – 0xFFFF_FFFF` | SLIP-0044 (HD wallet coin types) | Bitcoin = `0x8000_0000`, Solana = `0x8000_01F5` | -/// -/// Use `SLIP0044_NAMESPACE | ` to build SLIP-0044 identifiers. -/// Canonical constants for well-known chains are defined in `protocol-core`. -/// Governance MUST follow this convention when registering chains via `add_supported_chain`. -pub type ChainId = u32; - -/// Bitmask to set bit 31, placing a coin type in the SLIP-0044 namespace. -/// Example: `SLIP0044_NAMESPACE | 0` = Bitcoin, `SLIP0044_NAMESPACE | 501` = Solana. -pub const SLIP0044_NAMESPACE: u32 = 0x8000_0000; - -pub type ExternalAddr = BoundedVec>; - -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen, - scale_codec::DecodeWithMemTracking -)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -/// Signature verification scheme used to prove ownership of a cross-chain wallet. -/// -/// # Design invariant -/// -/// Every variant MUST represent an **external, non-Substrate-native** signing scheme. -/// Substrate-native schemes (Sr25519, Ed25519-Substrate, etc.) MUST NOT be added here -/// because an `AccountId32` is already self-proving in the Substrate key model — no -/// cross-chain proof of ownership is required or meaningful. `add_supported_chain` -/// enforces this invariant at the governance call level via `is_for_external_chain()`. -pub enum SignatureScheme { - /// Ethereum-style EIP-191 (ECDSA over Keccak256). - Eip191, - /// Solana-style raw Ed25519 (not the Substrate Ed25519 wrapper). - Ed25519, -} - -impl SignatureScheme { - /// Returns `true` if this scheme belongs to a non-Substrate-native ecosystem. - /// - /// All current variants are external. Any future variant for a Substrate-native - /// scheme MUST return `false` here so that `add_supported_chain` rejects it. - pub fn is_for_external_chain(&self) -> bool { - match self { - SignatureScheme::Eip191 => true, - SignatureScheme::Ed25519 => true, - } - } -} - -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen, - Default -)] -pub struct AccountMetadata { - pub display_name: Option>>, - pub bio: Option>>, - pub avatar: Option>>, -} - -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen -)] -pub struct ChainLink { - pub chain_id: ChainId, - pub address: ExternalAddr, -} - -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - TypeInfo, - MaxEncodedLen -)] -pub struct PrivateChainLink { - pub chain_id: ChainId, - pub commitment: [u8; 32], -} diff --git a/frame/account-mapping/src/utils.rs b/frame/account-mapping/src/utils.rs deleted file mode 100644 index 281d2f44..00000000 --- a/frame/account-mapping/src/utils.rs +++ /dev/null @@ -1,114 +0,0 @@ -extern crate alloc; -use alloc::vec::Vec; - -use sp_core::H160; -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; - -#[derive(Debug, PartialEq, Eq)] -pub enum AliasValidationError { - TooShort, - InvalidCharacters, -} - -pub fn validate_alias(bytes: &[u8]) -> Result<(), AliasValidationError> { - if bytes.len() < 3 { - return Err(AliasValidationError::TooShort); - } - if !bytes - .iter() - .all(|&b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_') - { - return Err(AliasValidationError::InvalidCharacters); - } - Ok(()) -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SignatureError { - RecoveryFailed, - AddressMismatch, -} - -pub fn eip191_message_hash(account_id_bytes: &[u8]) -> [u8; 32] { - let mut message: Vec = b"\x19Ethereum Signed Message:\n32".to_vec(); - let msg_hash = keccak_256(account_id_bytes); - message.extend_from_slice(&msg_hash); - keccak_256(&message) -} - -pub fn verify_ethereum_signature( - expected_address: &[u8], - account_id_bytes: &[u8], - signature: &[u8; 65], -) -> Result { - let final_hash = eip191_message_hash(account_id_bytes); - - let full_pubkey = secp256k1_ecdsa_recover(signature, &final_hash) - .map_err(|_| SignatureError::RecoveryFailed)?; - - let addr_hash = keccak_256(&full_pubkey[..]); - let recovered_addr = &addr_hash[12..]; - - if recovered_addr == expected_address { - let mut addr_bytes = [0u8; 20]; - addr_bytes.copy_from_slice(recovered_addr); - Ok(H160::from(addr_bytes)) - } else { - Err(SignatureError::AddressMismatch) - } -} - -pub fn verify_ed25519_signature( - expected_address: &[u8], - account_id_bytes: &[u8], - signature: &[u8; 64], -) -> Result<(), SignatureError> { - if sp_io::crypto::ed25519_verify( - &sp_core::ed25519::Signature::from_raw(*signature), - account_id_bytes, - &sp_core::ed25519::Public::from_raw( - expected_address - .try_into() - .map_err(|_| SignatureError::AddressMismatch)?, - ), - ) { - Ok(()) - } else { - Err(SignatureError::RecoveryFailed) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn validate_alias_rejects_short() { - assert_eq!(validate_alias(b"ab"), Err(AliasValidationError::TooShort)); - assert_eq!(validate_alias(b""), Err(AliasValidationError::TooShort)); - } - - #[test] - fn validate_alias_rejects_invalid_chars() { - assert_eq!( - validate_alias(b"Bad@Alias"), - Err(AliasValidationError::InvalidCharacters) - ); - assert_eq!( - validate_alias(b"UPPER"), - Err(AliasValidationError::InvalidCharacters) - ); - assert_eq!( - validate_alias(b"with space"), - Err(AliasValidationError::InvalidCharacters) - ); - } - - #[test] - fn validate_alias_accepts_valid() { - assert!(validate_alias(b"nolasco").is_ok()); - assert!(validate_alias(b"user_123").is_ok()); - assert!(validate_alias(b"abc").is_ok()); - assert!(validate_alias(b"0x0").is_ok()); - } -} diff --git a/frame/account-mapping/src/weights.rs b/frame/account-mapping/src/weights.rs deleted file mode 100644 index e448c2c5..00000000 --- a/frame/account-mapping/src/weights.rs +++ /dev/null @@ -1,783 +0,0 @@ - -//! Autogenerated weights for pallet_account_mapping -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` -//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/release/orbinum-node -// benchmark -// pallet -// --chain -// dev -// --pallet -// pallet_account_mapping -// --extrinsic -// * -// --steps -// 50 -// --repeat -// 20 -// --execution=wasm -// --wasm-execution=compiled -// --heap-pages=4096 -// --output -// ./frame/account-mapping/src/weights.rs -// --template -// ./scripts/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; - -/// Weight functions needed for pallet_account_mapping. -pub trait WeightInfo { - fn map_account() -> Weight; - fn unmap_account() -> Weight; - fn register_alias() -> Weight; - fn release_alias() -> Weight; - fn transfer_alias() -> Weight; - fn put_alias_on_sale() -> Weight; - fn cancel_sale() -> Weight; - fn buy_alias() -> Weight; - fn add_chain_link() -> Weight; - fn remove_chain_link() -> Weight; - fn set_account_metadata() -> Weight; - fn dispatch_as_linked_account() -> Weight; - fn register_private_link() -> Weight; - fn remove_private_link() -> Weight; - fn reveal_private_link() -> Weight; - fn dispatch_as_private_link() -> Weight; -} - -/// Weights for pallet_account_mapping using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn map_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `19` - // Estimated: `1504` - // Minimum execution time: 7_860_000 picoseconds. - Weight::from_parts(8_241_000, 1504) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `AccountMapping::OriginalAccounts` (r:1 w:1) - /// Proof: `AccountMapping::OriginalAccounts` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::MappedAccounts` (r:0 w:1) - /// Proof: `AccountMapping::MappedAccounts` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn unmap_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `189` - // Estimated: `3533` - // Minimum execution time: 15_642_000 picoseconds. - Weight::from_parts(16_022_000, 3533) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:1) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn register_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `5728` - // Minimum execution time: 34_708_000 picoseconds. - Weight::from_parts(35_921_000, 5728) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:1) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:0 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - fn release_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `257` - // Estimated: `5728` - // Minimum execution time: 38_843_000 picoseconds. - Weight::from_parts(39_824_000, 5728) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:2 w:2) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:0 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - fn transfer_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `360` - // Estimated: `6102` - // Minimum execution time: 63_378_000 picoseconds. - Weight::from_parts(65_651_000, 6102) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn put_alias_on_sale() -> Weight { - // Proof Size summary in bytes: - // Measured: `186` - // Estimated: `4172` - // Minimum execution time: 18_706_000 picoseconds. - Weight::from_parts(19_388_000, 4172) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn cancel_sale() -> Weight { - // Proof Size summary in bytes: - // Measured: `256` - // Estimated: `4172` - // Minimum execution time: 19_767_000 picoseconds. - Weight::from_parts(20_389_000, 4172) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:2) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `Balances::TotalIssuance` (r:1 w:0) - /// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn buy_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `546` - // Estimated: `5728` - // Minimum execution time: 112_476_000 picoseconds. - Weight::from_parts(114_447_000, 5728) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn add_chain_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `299` - // Estimated: `5728` - // Minimum execution time: 65_270_000 picoseconds. - Weight::from_parts(66_702_000, 5728) - .saturating_add(T::DbWeight::get().reads(7_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn remove_chain_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `286` - // Estimated: `5728` - // Minimum execution time: 22_371_000 picoseconds. - Weight::from_parts(23_353_000, 5728) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::AccountMetadatas` (r:0 w:1) - /// Proof: `AccountMapping::AccountMetadatas` (`max_values`: None, `max_size`: Some(441), added: 2916, mode: `MaxEncodedLen`) - fn set_account_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `186` - // Estimated: `3546` - // Minimum execution time: 16_694_000 picoseconds. - Weight::from_parts(17_394_000, 3546) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::ReverseChainLinks` (r:1 w:0) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn dispatch_as_linked_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `248` - // Estimated: `3647` - // Minimum execution time: 59_002_000 picoseconds. - Weight::from_parts(62_737_000, 3647) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn register_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `237` - // Estimated: `4091` - // Minimum execution time: 20_379_000 picoseconds. - Weight::from_parts(20_639_000, 4091) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn remove_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `281` - // Estimated: `4091` - // Minimum execution time: 21_210_000 picoseconds. - Weight::from_parts(22_140_000, 4091) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn reveal_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `393` - // Estimated: `5728` - // Minimum execution time: 129_108_000 picoseconds. - Weight::from_parts(135_598_000, 5728) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:0) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn dispatch_as_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `281` - // Estimated: `4091` - // Minimum execution time: 21_930_000 picoseconds. - Weight::from_parts(22_662_000, 4091) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn map_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `19` - // Estimated: `1504` - // Minimum execution time: 7_860_000 picoseconds. - Weight::from_parts(8_241_000, 1504) - .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `AccountMapping::OriginalAccounts` (r:1 w:1) - /// Proof: `AccountMapping::OriginalAccounts` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::MappedAccounts` (r:0 w:1) - /// Proof: `AccountMapping::MappedAccounts` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) - fn unmap_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `189` - // Estimated: `3533` - // Minimum execution time: 15_642_000 picoseconds. - Weight::from_parts(16_022_000, 3533) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:1) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn register_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `61` - // Estimated: `5728` - // Minimum execution time: 34_708_000 picoseconds. - Weight::from_parts(35_921_000, 5728) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:1) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:0 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - fn release_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `257` - // Estimated: `5728` - // Minimum execution time: 38_843_000 picoseconds. - Weight::from_parts(39_824_000, 5728) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:2 w:2) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:0 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - fn transfer_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `360` - // Estimated: `6102` - // Minimum execution time: 63_378_000 picoseconds. - Weight::from_parts(65_651_000, 6102) - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn put_alias_on_sale() -> Weight { - // Proof Size summary in bytes: - // Measured: `186` - // Estimated: `4172` - // Minimum execution time: 18_706_000 picoseconds. - Weight::from_parts(19_388_000, 4172) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn cancel_sale() -> Weight { - // Proof Size summary in bytes: - // Measured: `256` - // Estimated: `4172` - // Minimum execution time: 19_767_000 picoseconds. - Weight::from_parts(20_389_000, 4172) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:2) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::AliasListings` (r:1 w:1) - /// Proof: `AccountMapping::AliasListings` (`max_values`: None, `max_size`: Some(707), added: 3182, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `Balances::TotalIssuance` (r:1 w:0) - /// Proof: `Balances::TotalIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn buy_alias() -> Weight { - // Proof Size summary in bytes: - // Measured: `546` - // Estimated: `5728` - // Minimum execution time: 112_476_000 picoseconds. - Weight::from_parts(114_447_000, 5728) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn add_chain_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `299` - // Estimated: `5728` - // Minimum execution time: 65_270_000 picoseconds. - Weight::from_parts(66_702_000, 5728) - .saturating_add(RocksDbWeight::get().reads(7_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn remove_chain_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `286` - // Estimated: `5728` - // Minimum execution time: 22_371_000 picoseconds. - Weight::from_parts(23_353_000, 5728) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::AccountMetadatas` (r:0 w:1) - /// Proof: `AccountMapping::AccountMetadatas` (`max_values`: None, `max_size`: Some(441), added: 2916, mode: `MaxEncodedLen`) - fn set_account_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `186` - // Estimated: `3546` - // Minimum execution time: 16_694_000 picoseconds. - Weight::from_parts(17_394_000, 3546) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::ReverseChainLinks` (r:1 w:0) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn dispatch_as_linked_account() -> Weight { - // Proof Size summary in bytes: - // Measured: `248` - // Estimated: `3647` - // Minimum execution time: 59_002_000 picoseconds. - Weight::from_parts(62_737_000, 3647) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn register_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `237` - // Estimated: `4091` - // Minimum execution time: 20_379_000 picoseconds. - Weight::from_parts(20_639_000, 4091) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn remove_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `281` - // Estimated: `4091` - // Minimum execution time: 21_210_000 picoseconds. - Weight::from_parts(22_140_000, 4091) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:1) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::SupportedChains` (r:1 w:0) - /// Proof: `AccountMapping::SupportedChains` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::Identities` (r:1 w:1) - /// Proof: `AccountMapping::Identities` (`max_values`: None, `max_size`: Some(2263), added: 4738, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `AccountMapping::ReverseChainLinks` (r:0 w:1) - /// Proof: `AccountMapping::ReverseChainLinks` (`max_values`: None, `max_size`: Some(182), added: 2657, mode: `MaxEncodedLen`) - fn reveal_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `393` - // Estimated: `5728` - // Minimum execution time: 129_108_000 picoseconds. - Weight::from_parts(135_598_000, 5728) - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) - } - /// Storage: `AccountMapping::AccountAliases` (r:1 w:0) - /// Proof: `AccountMapping::AccountAliases` (`max_values`: None, `max_size`: Some(81), added: 2556, mode: `MaxEncodedLen`) - /// Storage: `AccountMapping::PrivateChainLinks` (r:1 w:0) - /// Proof: `AccountMapping::PrivateChainLinks` (`max_values`: None, `max_size`: Some(626), added: 3101, mode: `MaxEncodedLen`) - /// Storage: `System::Number` (r:1 w:0) - /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::ExecutionPhase` (r:1 w:0) - /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) - /// Storage: `System::EventCount` (r:1 w:1) - /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Events` (r:1 w:1) - /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn dispatch_as_private_link() -> Weight { - // Proof Size summary in bytes: - // Measured: `281` - // Estimated: `4091` - // Minimum execution time: 21_930_000 picoseconds. - Weight::from_parts(22_662_000, 4091) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } -} diff --git a/frame/evm/precompile/account-mapping/CHANGELOG.md b/frame/evm/precompile/account-mapping/CHANGELOG.md deleted file mode 100644 index e4cf2279..00000000 --- a/frame/evm/precompile/account-mapping/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -# Changelog - -All notable changes to `pallet-evm-precompile-account-mapping` will be documented in this file. - -## [0.2.0] - 2026-05-29 - -### Added - -- **`dispatchAsLinkedAccount` precompile selector (`0x0630cef9`)** — exposes `pallet_account_mapping::dispatch_as_linked_account` to EVM clients. Any EVM address can relay a SCALE-encoded `RuntimeCall` on behalf of a Substrate account, authorized by the external wallet registered as a chain link. The external wallet signs `eip191(keccak256(SCALE(call)))` without needing to interact with the Substrate RPC layer. -- Added `use alloc::boxed::Box` import required for `no_std` / WASM target compatibility. -- Added `use scale_codec::Decode` import and `scale-codec` dependency for decoding `AccountId32` and `RuntimeCall` from ABI input. -- Three new unit tests in `tests.rs`: - - `dispatch_as_linked_account_short_input_returns_error` — verifies inputs shorter than 160 bytes return an ABI error. - - `dispatch_as_linked_account_invalid_scale_call_returns_error` — verifies invalid SCALE bytes trigger a decode error. - - `dispatch_as_linked_account_routes_to_pallet_with_valid_abi` — verifies a fully ABI-encoded call routes correctly to the pallet. - -## [0.1.0] - 2026-05-14 - -### Added - -- Initial release. -- EVM precompile for `pallet-account-mapping` at address `0x0000000000000000000000000000000000000800`. -- Selectors: `mapAccount`, `unmapAccount`, `isEvmSuffixAccount`, `toEvmAddress`, `resolveAliasFull`, `registerAlias`, `releaseAlias`, `transferAlias`, `putAliasOnSale`, `cancelSale`, `buyAlias`, `resolveAlias`, `getAliasOf`, `addChainLink`, `removeChainLink`, `registerPrivateLink`, `removePrivateLink`, `revealPrivateLink`, `hasPrivateLink`, `setAccountMetadata`. diff --git a/frame/evm/precompile/account-mapping/Cargo.toml b/frame/evm/precompile/account-mapping/Cargo.toml deleted file mode 100644 index 76b75988..00000000 --- a/frame/evm/precompile/account-mapping/Cargo.toml +++ /dev/null @@ -1,53 +0,0 @@ -[package] -name = "pallet-evm-precompile-account-mapping" -version = "0.2.0" -authors = { workspace = true } -edition = "2021" -description = "EVM Precompile for Orbinum Account Mapping Pallet." -license = "Apache-2.0" - -[dependencies] -# Substrate CORE -sp-core = { workspace = true } -sp-runtime = { workspace = true } -sp-std = { workspace = true } - -# Codec -scale-codec = { workspace = true } - -# Substrate FRAME -frame-support = { workspace = true } -frame-system = { workspace = true } - -# Frontier EVM -fp-evm = { workspace = true } -pallet-evm = { workspace = true } -precompile-utils = { workspace = true } - -# Orbinum Pallets -pallet-account-mapping = { workspace = true } - -[dev-dependencies] -frame-system = { workspace = true, features = ["default"] } -pallet-balances = { workspace = true, features = ["default"] } -pallet-timestamp = { workspace = true, features = ["default"] } -scale-codec = { workspace = true, features = ["derive", "std"] } -scale-info = { workspace = true, features = ["derive", "std"] } -sp-core = { workspace = true, features = ["default"] } -sp-io = { workspace = true, features = ["default"] } -sp-runtime = { workspace = true, features = ["default"] } - -[features] -default = ["std"] -std = [ - "sp-core/std", - "sp-std/std", - "sp-runtime/std", - "scale-codec/std", - "frame-support/std", - "frame-system/std", - "fp-evm/std", - "pallet-evm/std", - "precompile-utils/std", - "pallet-account-mapping/std", -] diff --git a/frame/evm/precompile/account-mapping/src/lib.rs b/frame/evm/precompile/account-mapping/src/lib.rs deleted file mode 100644 index 941add6d..00000000 --- a/frame/evm/precompile/account-mapping/src/lib.rs +++ /dev/null @@ -1,822 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - -use alloc::{boxed::Box, format, vec, vec::Vec}; -use core::marker::PhantomData; - -use scale_codec::Decode; - -use fp_evm::{ - ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput, - PrecompileResult, -}; -use frame_support::{dispatch::GetDispatchInfo, traits::ConstU32, BoundedVec}; -use pallet_evm::{AddressMapping, GasWeightMapping}; -use sp_core::{H160, U256}; -use sp_runtime::traits::{Convert, Dispatchable}; - -// ───────────────────────────────────────────────────────────────────────────── -// ABI Function Selectors -// -// Computed as: bytes4(keccak256("functionName(argTypes)")) -// Verify with: cast sig "functionName(argTypes)" (Foundry) -// -// ─── Read-only ─────────────────────────────────────────────────────────────── -// resolveAlias(string) → 0xd03149ab -const SEL_RESOLVE_ALIAS: [u8; 4] = [0xd0, 0x31, 0x49, 0xab]; -// getAliasOf(address) → 0x7a0ed62c -const SEL_GET_ALIAS_OF: [u8; 4] = [0x7a, 0x0e, 0xd6, 0x2c]; -// hasPrivateLink(string,bytes32) → 0x47e05c6c -const SEL_HAS_PRIVATE_LINK: [u8; 4] = [0x47, 0xe0, 0x5c, 0x6c]; -// -// ─── State-changing: no arguments ──────────────────────────────────────────── -// mapAccount() → 0xdca49d0e -const SEL_MAP_ACCOUNT: [u8; 4] = [0xdc, 0xa4, 0x9d, 0x0e]; -// unmapAccount() → 0x08f57367 -const SEL_UNMAP_ACCOUNT: [u8; 4] = [0x08, 0xf5, 0x73, 0x67]; -// releaseAlias() → 0x7fac359e -const SEL_RELEASE_ALIAS: [u8; 4] = [0x7f, 0xac, 0x35, 0x9e]; -// cancelSale() → 0x4d023ab9 -const SEL_CANCEL_SALE: [u8; 4] = [0x4d, 0x02, 0x3a, 0xb9]; -// -// ─── State-changing: with arguments ────────────────────────────────────────── -// registerAlias(string) → 0x2f8839c3 -const SEL_REGISTER_ALIAS: [u8; 4] = [0x2f, 0x88, 0x39, 0xc3]; -// transferAlias(address) → 0x5ac998e7 -const SEL_TRANSFER_ALIAS: [u8; 4] = [0x5a, 0xc9, 0x98, 0xe7]; -// buyAlias(string) → 0x1625df3a -const SEL_BUY_ALIAS: [u8; 4] = [0x16, 0x25, 0xdf, 0x3a]; -// putAliasOnSale(uint256,address[]) → 0x32091192 -const SEL_PUT_ALIAS_ON_SALE: [u8; 4] = [0x32, 0x09, 0x11, 0x92]; -// removeChainLink(uint32) → 0x6f579c0c -const SEL_REMOVE_CHAIN_LINK: [u8; 4] = [0x6f, 0x57, 0x9c, 0x0c]; -// addChainLink(uint32,bytes,bytes) → 0x5f3e837c -const SEL_ADD_CHAIN_LINK: [u8; 4] = [0x5f, 0x3e, 0x83, 0x7c]; -// registerPrivateLink(uint32,bytes32) → 0xc04e98f4 -const SEL_REGISTER_PRIVATE_LINK: [u8; 4] = [0xc0, 0x4e, 0x98, 0xf4]; -// removePrivateLink(bytes32) → 0xdfd8b57e -const SEL_REMOVE_PRIVATE_LINK: [u8; 4] = [0xdf, 0xd8, 0xb5, 0x7e]; -// revealPrivateLink(bytes32,bytes,bytes32,bytes)→ 0x4df1f33d -const SEL_REVEAL_PRIVATE_LINK: [u8; 4] = [0x4d, 0xf1, 0xf3, 0x3d]; -// setAccountMetadata(bytes,bytes,bytes) → 0x776cf9ff -const SEL_SET_METADATA: [u8; 4] = [0x77, 0x6c, 0xf9, 0xff]; -// -// ─── Phase 3 helpers — composability with BalancesPrecompile ───────────────── -// isEvmSuffixAccount(bytes32) → 0x96e69f8a -const SEL_IS_EVM_SUFFIX: [u8; 4] = [0x96, 0xe6, 0x9f, 0x8a]; -// toEvmAddress(bytes32) → 0x0f5b3052 -const SEL_TO_EVM_ADDRESS: [u8; 4] = [0x0f, 0x5b, 0x30, 0x52]; -// resolveAliasFull(string) → 0x2e40772b -const SEL_RESOLVE_ALIAS_FULL: [u8; 4] = [0x2e, 0x40, 0x77, 0x2b]; -// -// ─── Phase 4 (Unification Plan Fase 2) — relay via chain link ──────────────── -// dispatchAsLinkedAccount(bytes32,uint32,bytes,bytes,bytes) → 0x0630cef9 -const SEL_DISPATCH_AS_LINKED: [u8; 4] = [0x06, 0x30, 0xce, 0xf9]; -// ───────────────────────────────────────────────────────────────────────────── - -pub struct AccountMappingPrecompile(PhantomData); - -impl Precompile for AccountMappingPrecompile -where - T: pallet_evm::Config + pallet_account_mapping::Config, - ::RuntimeCall: Dispatchable - + GetDispatchInfo - + From>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From::AccountId>>, - <::RuntimeCall as Dispatchable>::PostInfo: core::fmt::Debug, - pallet_evm::AccountIdOf: Into<::AccountId>, - pallet_account_mapping::BalanceOf: TryFrom, -{ - fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult { - let input = handle.input().to_vec(); - - if input.len() < 4 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("input too short: missing selector".into()), - }); - } - - let selector: [u8; 4] = input[0..4].try_into().unwrap(); - - match selector { - // ─── Read-only ─────────────────────────────────────────────────── - SEL_RESOLVE_ALIAS => Self::resolve_alias(&input), - SEL_GET_ALIAS_OF => Self::get_alias_of(&input), - SEL_HAS_PRIVATE_LINK => Self::has_private_link(&input), - - // ─── No-arg dispatches ─────────────────────────────────────────── - SEL_MAP_ACCOUNT => { - Self::dispatch_call(handle, pallet_account_mapping::Call::::map_account {}) - } - SEL_UNMAP_ACCOUNT => { - Self::dispatch_call(handle, pallet_account_mapping::Call::::unmap_account {}) - } - SEL_RELEASE_ALIAS => { - Self::dispatch_call(handle, pallet_account_mapping::Call::::release_alias {}) - } - SEL_CANCEL_SALE => { - Self::dispatch_call(handle, pallet_account_mapping::Call::::cancel_sale {}) - } - - // ─── With-arg dispatches ───────────────────────────────────────── - SEL_REGISTER_ALIAS => Self::register_alias(handle, &input), - SEL_TRANSFER_ALIAS => Self::transfer_alias(handle, &input), - SEL_BUY_ALIAS => Self::buy_alias(handle, &input), - SEL_PUT_ALIAS_ON_SALE => Self::put_alias_on_sale(handle, &input), - SEL_REMOVE_CHAIN_LINK => Self::remove_chain_link(handle, &input), - SEL_ADD_CHAIN_LINK => Self::add_chain_link(handle, &input), - SEL_REGISTER_PRIVATE_LINK => Self::register_private_link(handle, &input), - SEL_REMOVE_PRIVATE_LINK => Self::remove_private_link(handle, &input), - SEL_REVEAL_PRIVATE_LINK => Self::reveal_private_link(handle, &input), - SEL_SET_METADATA => Self::set_account_metadata(handle, &input), - - // ─── Phase 3: composability helpers ───────────────────────────── - SEL_IS_EVM_SUFFIX => Self::is_evm_suffix_account(&input), - SEL_TO_EVM_ADDRESS => Self::to_evm_address(&input), - SEL_RESOLVE_ALIAS_FULL => Self::resolve_alias_full(&input), - - // ─── Phase 4: relay via chain link ─────────────────────────────── - SEL_DISPATCH_AS_LINKED => Self::dispatch_as_linked_account(handle, &input), - - _ => Err(PrecompileFailure::Error { - exit_status: ExitError::Other("unknown selector".into()), - }), - } - } -} - -impl AccountMappingPrecompile -where - T: pallet_evm::Config + pallet_account_mapping::Config, - ::RuntimeCall: Dispatchable - + GetDispatchInfo - + From>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - From::AccountId>>, - <::RuntimeCall as Dispatchable>::PostInfo: core::fmt::Debug, - pallet_evm::AccountIdOf: Into<::AccountId>, - pallet_account_mapping::BalanceOf: TryFrom, -{ - // ───────────────────────────────────────────────────────────────────────── - // Dispatch helper - // ───────────────────────────────────────────────────────────────────────── - - /// Dispatches any `pallet_account_mapping::Call` on behalf of the EVM caller. - /// Records gas cost from the call's weight info before dispatching. - fn dispatch_call( - handle: &mut impl PrecompileHandle, - call: pallet_account_mapping::Call, - ) -> PrecompileResult { - let runtime_call = <::RuntimeCall as From< - pallet_account_mapping::Call, - >>::from(call); - let info = runtime_call.get_dispatch_info(); - let gas_cost = T::GasWeightMapping::weight_to_gas(info.total_weight()); - handle.record_cost(gas_cost)?; - - let caller_account: ::AccountId = - T::AddressMapping::into_account_id(handle.context().caller).into(); - let origin = - <::RuntimeCall as Dispatchable>::RuntimeOrigin::from(Some( - caller_account, - )); - - match runtime_call.dispatch(origin) { - Ok(_) => Ok(PrecompileOutput { - exit_status: ExitSucceed::Stopped, - output: Default::default(), - }), - Err(e) => Err(PrecompileFailure::Error { - exit_status: ExitError::Other(format!("{e:?}").into()), - }), - } - } - - // ───────────────────────────────────────────────────────────────────────── - // State-changing: with arguments - // ───────────────────────────────────────────────────────────────────────── - - /// registerAlias(string alias) - /// - /// ABI params (input[4..]): - /// [0..32] uint256 offset → string data (dynamic) - /// at offset: uint256 length + UTF-8 bytes of the alias - fn register_alias(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let alias_bytes = Self::decode_abi_string(&input[4..])?; - let bounded: pallet_account_mapping::AliasOf = - alias_bytes - .try_into() - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("alias too long".into()), - })?; - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::register_alias { alias: bounded }, - ) - } - - /// transferAlias(address newOwner) - /// - /// ABI params (input[4..]): - /// [0..32] address (20 bytes, right-justified in 32-byte slot) - fn transfer_alias(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 32 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("transferAlias: input too short".into()), - }); - } - let h160 = H160::from_slice(¶ms[12..32]); - let new_owner: ::AccountId = - T::AddressMapping::into_account_id(h160).into(); - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::transfer_alias { new_owner }, - ) - } - - /// buyAlias(string alias) - /// - /// Same ABI layout as registerAlias(string). - fn buy_alias(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let alias_bytes = Self::decode_abi_string(&input[4..])?; - let bounded: pallet_account_mapping::AliasOf = - alias_bytes - .try_into() - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other("alias too long".into()), - })?; - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::buy_alias { alias: bounded }, - ) - } - - /// putAliasOnSale(uint256 price, address[] allowedBuyers) - /// - /// ABI params (input[4..]): - /// [0..32] uint256 price (static) - /// [32..64] uint256 offset → address[] (dynamic) - /// at offset: uint256 array_length, then N × 32-byte address slots - /// - /// Empty array → public listing (no whitelist, allowed_buyers = None). - fn put_alias_on_sale(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 64 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("putAliasOnSale: input too short".into()), - }); - } - - // price - let price_u128 = U256::from_big_endian(¶ms[0..32]).low_u128(); - let price = pallet_account_mapping::BalanceOf::::try_from(price_u128).map_err(|_| { - PrecompileFailure::Error { - exit_status: ExitError::Other("putAliasOnSale: price overflow".into()), - } - })?; - - // address[] whitelist - let arr_offset = U256::from_big_endian(¶ms[32..64]).low_u32() as usize; - let buyers: Option::AccountId>> = - if arr_offset + 32 > params.len() { - None - } else { - let arr_len = - U256::from_big_endian(¶ms[arr_offset..arr_offset + 32]).low_u32() as usize; - if arr_len == 0 { - None - } else { - let data_start = arr_offset + 32; - if data_start + arr_len * 32 > params.len() { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - "putAliasOnSale: whitelist out of bounds".into(), - ), - }); - } - let mut list = vec![]; - for i in 0..arr_len { - let slot = data_start + i * 32; - let addr = H160::from_slice(¶ms[slot + 12..slot + 32]); - let account: ::AccountId = - T::AddressMapping::into_account_id(addr).into(); - list.push(account); - } - Some(list) - } - }; - - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::put_alias_on_sale { - price, - allowed_buyers: buyers, - }, - ) - } - - /// removeChainLink(uint32 chainId) - /// - /// ABI params (input[4..]): - /// [0..32] uint32 (value in last 4 bytes of slot) - fn remove_chain_link(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let chain_id = Self::decode_u32(&input[4..])?; - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::remove_chain_link { chain_id }, - ) - } - - /// addChainLink(uint32 chainId, bytes externalAddr, bytes signature) - /// - /// ABI params (input[4..]) — mixed static + dynamic: - /// [0..32] uint32 chainId (static) - /// [32..64] uint256 offset → bytes externalAddr (dynamic) - /// [64..96] uint256 offset → bytes signature (dynamic) - /// at each offset: uint256 length + raw bytes (zero-padded to 32-byte multiple) - /// - /// Note: the signature scheme (Eip191 / Ed25519) is NOT passed here; - /// the pallet looks it up from `SupportedChains` storage by chain_id. - fn add_chain_link(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 96 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("addChainLink: input too short".into()), - }); - } - let chain_id = U256::from_big_endian(¶ms[0..32]).low_u32(); - let address = Self::decode_bytes_at_slot(params, 32)?; - let signature = Self::decode_bytes_at_slot(params, 64)?; - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::add_chain_link { - chain_id, - address, - signature, - }, - ) - } - - /// registerPrivateLink(uint32 chainId, bytes32 commitment) - /// - /// ABI params (input[4..]): - /// [0..32] uint32 chain_id (padded) - /// [32..64] bytes32 commitment (static) - fn register_private_link(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 64 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("registerPrivateLink: input too short".into()), - }); - } - let chain_id = U256::from_big_endian(¶ms[0..32]).low_u32(); - let commitment: [u8; 32] = params[32..64].try_into().unwrap(); - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::register_private_link { - chain_id, - commitment, - }, - ) - } - - /// removePrivateLink(bytes32 commitment) - /// - /// ABI params (input[4..]): - /// [0..32] bytes32 commitment (static) - fn remove_private_link(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 32 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("removePrivateLink: input too short".into()), - }); - } - let commitment: [u8; 32] = params[0..32].try_into().unwrap(); - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::remove_private_link { commitment }, - ) - } - - /// revealPrivateLink(bytes32 commitment, bytes address, bytes32 blinding, bytes signature) - /// - /// ABI params (input[4..]) — mixed static + dynamic: - /// [0..32] bytes32 commitment (static) - /// [32..64] uint256 offset → bytes address (dynamic) - /// [64..96] bytes32 blinding (static) - /// [96..128] uint256 offset → bytes signature (dynamic) - /// at each offset: uint256 length + raw bytes - fn reveal_private_link(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 128 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("revealPrivateLink: input too short".into()), - }); - } - let commitment: [u8; 32] = params[0..32].try_into().unwrap(); - let address = Self::decode_bytes_at_slot(params, 32)?; - let blinding: [u8; 32] = params[64..96].try_into().unwrap(); - let signature = Self::decode_bytes_at_slot(params, 96)?; - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::reveal_private_link { - commitment, - address, - blinding, - signature, - }, - ) - } - - /// setAccountMetadata(bytes displayName, bytes bio, bytes avatar) - /// - /// ABI params (input[4..]) — all dynamic: - /// [0..32] uint256 offset → bytes displayName - /// [32..64] uint256 offset → bytes bio - /// [64..96] uint256 offset → bytes avatar - /// at each offset: uint256 length + UTF-8 bytes - /// - /// Empty bytes → field not changed (None). Max 128 bytes each. - fn set_account_metadata(handle: &mut impl PrecompileHandle, input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 96 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("setAccountMetadata: input too short".into()), - }); - } - - let raw_display = Self::decode_bytes_at_slot(params, 0)?; - let raw_bio = Self::decode_bytes_at_slot(params, 32)?; - let raw_avatar = Self::decode_bytes_at_slot(params, 64)?; - - // Converts raw bytes → Option>> - // Empty bytes → None (field unchanged). - let to_opt_bounded = - |v: Vec| -> Result>>, PrecompileFailure> { - if v.is_empty() { - Ok(None) - } else { - v.try_into() - .map(Some) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other( - "metadata field too long (max 128 bytes)".into(), - ), - }) - } - }; - - let display_name = to_opt_bounded(raw_display)?; - let bio = to_opt_bounded(raw_bio)?; - let avatar = to_opt_bounded(raw_avatar)?; - - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::set_account_metadata { - display_name, - bio, - avatar, - }, - ) - } - - // ───────────────────────────────────────────────────────────────────────── - // Read-only queries - // ───────────────────────────────────────────────────────────────────────── - - /// resolveAlias(string alias) returns (address owner, address evmAddress) - /// - /// ABI return: two 32-byte address slots. - /// Returns 64 zero bytes if the alias does not exist. - fn resolve_alias(input: &[u8]) -> PrecompileResult { - let alias_bytes = Self::decode_abi_string(&input[4..])?; - - let (owner_h160, evm_h160) = if let Some(record) = - pallet_account_mapping::Pallet::::runtime_api_resolve_alias(&alias_bytes) - { - let h = record.evm_address.unwrap_or(H160::zero()); - (h, h) - } else { - (H160::zero(), H160::zero()) - }; - - let mut output = vec![0u8; 64]; - output[12..32].copy_from_slice(owner_h160.as_bytes()); - output[44..64].copy_from_slice(evm_h160.as_bytes()); - - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - /// getAliasOf(address evm) returns (bytes alias) - /// - /// ABI return: offset (0x20) + length + alias bytes, zero-padded. - /// Returns empty bytes if no alias is registered for the address. - fn get_alias_of(input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 32 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("getAliasOf: input too short".into()), - }); - } - - let evm_addr = H160::from_slice(¶ms[12..32]); - let account: ::AccountId = - T::AddressMapping::into_account_id(evm_addr).into(); - - let alias = pallet_account_mapping::Pallet::::runtime_api_get_alias_of(account) - .unwrap_or_default(); - - // ABI bytes: offset(32) + length + data (zero-padded to 32-byte multiple) - let padded_len = (alias.len() + 31) & !31; - let mut output = vec![0u8; 64 + padded_len]; - output[31] = 0x20; // offset = 0x20 - let len_bytes = (alias.len() as u64).to_be_bytes(); - output[56..64].copy_from_slice(&len_bytes); - output[64..64 + alias.len()].copy_from_slice(&alias); - - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - /// hasPrivateLink(string alias, bytes32 commitment) returns (bool) - /// - /// ABI params (input[4..]): - /// [0..32] uint256 offset → string alias (dynamic) - /// [32..64] bytes32 commitment (static) - /// - /// Returns ABI-encoded bool (32-byte slot, last byte = 1 or 0). - fn has_private_link(input: &[u8]) -> PrecompileResult { - let params = &input[4..]; - if params.len() < 64 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("hasPrivateLink: input too short".into()), - }); - } - - let commitment: [u8; 32] = params[32..64].try_into().unwrap(); - let alias_bytes = Self::decode_abi_string(params)?; - - let found = pallet_account_mapping::Pallet::::runtime_api_has_private_link( - alias_bytes, - commitment, - ); - - let mut output = vec![0u8; 32]; - if found { - output[31] = 1; - } - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - // ───────────────────────────────────────────────────────────────────────── - // ABI decode helpers - // ───────────────────────────────────────────────────────────────────────── - - /// Decodes a `uint32` from the first 32-byte slot of `params`. - fn decode_u32(params: &[u8]) -> Result { - if params.len() < 32 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("ABI: uint32 slot too short".into()), - }); - } - Ok(U256::from_big_endian(¶ms[0..32]).low_u32()) - } - - /// Decodes an ABI `bytes` (or `string`) value whose **offset pointer** lives - /// at `slot_start` bytes within `params`. - /// - /// Layout: - /// `params[slot_start..slot_start+32]` → absolute offset (relative to params start) - /// `params[offset..offset+32]` → uint256 byte length - /// `params[offset+32..]` → raw bytes (zero-padded to 32-byte multiple) - fn decode_bytes_at_slot( - params: &[u8], - slot_start: usize, - ) -> Result, PrecompileFailure> { - if slot_start + 32 > params.len() { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("ABI: slot out of bounds".into()), - }); - } - let offset = U256::from_big_endian(¶ms[slot_start..slot_start + 32]).low_u32() as usize; - if offset + 32 > params.len() { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("ABI: dynamic offset out of bounds".into()), - }); - } - let length = U256::from_big_endian(¶ms[offset..offset + 32]).low_u32() as usize; - let data_start = offset + 32; - if data_start + length > params.len() { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("ABI: bytes data out of bounds".into()), - }); - } - Ok(params[data_start..data_start + length].to_vec()) - } - - /// Decodes a top-level ABI-encoded `string` (or `bytes`) value. - /// The offset pointer lives at slot 0 of `params` (i.e. input stripped of selector). - fn decode_abi_string(params: &[u8]) -> Result, PrecompileFailure> { - Self::decode_bytes_at_slot(params, 0) - } - - // ───────────────────────────────────────────────────────────────────────── - // Phase 3: composability helpers - // ───────────────────────────────────────────────────────────────────────── - - /// isEvmSuffixAccount(bytes32 accountId) → bool - /// - /// Returns `true` if the 32-byte AccountId follows the `[H160 | 0x00×12]` - /// suffix pattern, indicating a secp256k1 account created with OrbinumSignature. - /// Pure computation — no storage lookup. - fn is_evm_suffix_account(input: &[u8]) -> PrecompileResult { - if input.len() < 36 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("isEvmSuffixAccount: input too short".into()), - }); - } - let account_bytes = &input[4..36]; - let is_suffix = account_bytes[20..].iter().all(|&b| b == 0); - - let mut output = vec![0u8; 32]; - output[31] = is_suffix as u8; - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - /// toEvmAddress(bytes32 accountId) → address - /// - /// For secp256k1 accounts (`[H160 | 0x00×12]` pattern): derives the H160 - /// implicitly — pure O(1) computation, no storage lookup. - /// - /// For Sr25519/Ed25519 accounts: returns `address(0)`. Their EVM address - /// is registered via `map_account` and is not derivable from the bytes32 - /// alone. Use `getAliasOf` or native Substrate RPCs instead. - fn to_evm_address(input: &[u8]) -> PrecompileResult { - if input.len() < 36 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("toEvmAddress: input too short".into()), - }); - } - let raw = &input[4..36]; - let is_suffix = raw[20..].iter().all(|&b| b == 0); - - let h160 = if is_suffix { - H160::from_slice(&raw[..20]) - } else { - H160::zero() - }; - - let mut output = vec![0u8; 32]; - output[12..32].copy_from_slice(h160.as_bytes()); - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - /// resolveAliasFull(string alias) → (bytes32 accountId32, address evmAddress, bool isEvmSuffix) - /// - /// Extended version of `resolveAlias` that exposes the full `AccountId32` (bytes32) - /// required by `BalancesPrecompile.transfer(bytes32, uint256)`, plus a flag that - /// indicates whether the account is a secp256k1 implicit-mapping account. - /// - /// ABI return layout (96 bytes): - /// [0..32] bytes32 accountId32 — AccountId32 bytes (secp256k1 only; zeros for Sr25519) - /// [32..64] address evmAddress — H160 (derived for secp256k1; from storage or zero for Sr25519) - /// [64..96] bool isEvmSuffix — true for secp256k1 OrbinumSignature accounts - /// - /// Returns 96 zero bytes if the alias does not exist. - /// - /// Note: for Sr25519/Ed25519 accounts, `accountId32` is zero — the AccountId32 is - /// not derivable from the alias record without a native Substrate API call. - fn resolve_alias_full(input: &[u8]) -> PrecompileResult { - let alias_bytes = Self::decode_abi_string(&input[4..])?; - - let Some(record) = - pallet_account_mapping::Pallet::::runtime_api_resolve_alias(&alias_bytes) - else { - return Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output: vec![0u8; 96], - }); - }; - - let is_suffix = T::is_implicit_evm_account(&record.owner); - - // For secp256k1: derive H160 implicitly (always correct for suffix accounts). - // For Sr25519/Ed25519: use the mapped evm_address from storage, or zero. - let evm_h160 = if is_suffix { - T::AccountIdToEvmAddress::convert(record.owner).unwrap_or(H160::zero()) - } else { - record.evm_address.unwrap_or(H160::zero()) - }; - - let mut output = vec![0u8; 96]; - // bytes32 accountId32 [0..32]: reconstructed from H160 for secp256k1 ([H160|0x00×12]). - // For Sr25519/Ed25519: left as zeros (not derivable without AsRef<[u8;32]>). - if is_suffix && evm_h160 != H160::zero() { - output[0..20].copy_from_slice(evm_h160.as_bytes()); // suffix [20..32] stays zero - } - output[44..64].copy_from_slice(evm_h160.as_bytes()); // address (slot [32..64]) - output[95] = is_suffix as u8; // bool (slot [64..96]) - - Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output, - }) - } - - // ───────────────────────────────────────────────────────────────────────── - // Phase 4: relay via chain link - // ───────────────────────────────────────────────────────────────────────── - - /// dispatchAsLinkedAccount(bytes32 owner, uint32 chainId, bytes address, bytes signature, bytes call) - /// - /// Relayer proxy: dispatches `call` as `owner`, verified by the external wallet - /// registered as chain link `(chainId, address)` for that owner. The EVM caller - /// (relayer) pays gas — only the external wallet's signature over the - /// SCALE-encoded `call` payload is verified. - /// - /// `call` must be **SCALE-encoded** `RuntimeCall` bytes. Produce off-chain via - /// Polkadot.js (`api.createType('Call', ...).toHex()`) or `@polkadot/api`. - /// - /// Note: chain_id == NativeEvmChainId (e.g. 2700 on testnet, 270 on mainnet) - /// is rejected by the pallet with `UseNativeSignatureForEvmAccounts`. - /// - /// ABI params (input[4..]) — mixed static + dynamic: - /// [0..32] bytes32 owner (static — raw AccountId32, SCALE-encoded as 32 flat bytes) - /// [32..64] uint32 chainId (static — value in last 4 bytes of slot) - /// [64..96] uint256 offset → bytes address (dynamic — external chain address) - /// [96..128] uint256 offset → bytes signature (dynamic — external wallet sig over SCALE(call)) - /// [128..160] uint256 offset → bytes call (dynamic — SCALE-encoded RuntimeCall) - fn dispatch_as_linked_account( - handle: &mut impl PrecompileHandle, - input: &[u8], - ) -> PrecompileResult { - let params = &input[4..]; - // Minimum: 5 slots × 32 = 160 bytes - if params.len() < 160 { - return Err(PrecompileFailure::Error { - exit_status: ExitError::Other("dispatchAsLinkedAccount: input too short".into()), - }); - } - - // bytes32 owner → AccountId32 - // AccountId32 SCALE-encodes as 32 flat bytes (no length prefix). - let owner = - ::AccountId::decode(&mut ¶ms[0..32]).map_err(|_| { - PrecompileFailure::Error { - exit_status: ExitError::Other( - "dispatchAsLinkedAccount: invalid owner AccountId".into(), - ), - } - })?; - - // uint32 chainId (value in last 4 bytes of 32-byte ABI slot) - let chain_id = u32::from_be_bytes(params[60..64].try_into().unwrap()); - - // bytes address, signature, call — dynamic args with offset pointers at slots 2, 3, 4 - let address = Self::decode_bytes_at_slot(params, 64)?; - let signature = Self::decode_bytes_at_slot(params, 96)?; - let call_bytes = Self::decode_bytes_at_slot(params, 128)?; - - // SCALE-decode the inner RuntimeCall - let inner_call = - ::RuntimeCall::decode(&mut &call_bytes[..]) - .map_err(|_| PrecompileFailure::Error { - exit_status: ExitError::Other( - "dispatchAsLinkedAccount: invalid SCALE-encoded call".into(), - ), - })?; - - Self::dispatch_call( - handle, - pallet_account_mapping::Call::::dispatch_as_linked_account { - owner, - chain_id, - address, - signature, - call: Box::new(inner_call), - }, - ) - } -} diff --git a/frame/evm/precompile/account-mapping/src/mock.rs b/frame/evm/precompile/account-mapping/src/mock.rs deleted file mode 100644 index 2d27b325..00000000 --- a/frame/evm/precompile/account-mapping/src/mock.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! Test mock for `pallet-evm-precompile-account-mapping`. -//! -//! Uses `AccountId = H160` so that `IdentityAddressMapping` works directly -//! (EVM caller H160 → substrate AccountId H160 without any conversion). -//! Uses `Balance = u128` so that `BalanceOf: TryFrom` is trivially -//! satisfied. - -use frame_support::{parameter_types, weights::Weight}; -use pallet_account_mapping::PrivateLinkVerifierPort; -use pallet_evm::{ - Context, EnsureAddressNever, EnsureAddressRoot, FeeCalculator, IdentityAddressMapping, - PrecompileHandle, -}; -use sp_core::{H160, H256, U256}; -use sp_runtime::{ - traits::{BlakeTwo256, Convert, IdentityLookup}, - BuildStorage, -}; - -use fp_evm::{ExitError, ExitReason, Transfer}; - -pub(crate) type Balance = u128; - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test { - System: frame_system, - Balances: pallet_balances, - Timestamp: pallet_timestamp, - EVM: pallet_evm, - AccountMapping: pallet_account_mapping, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; -} - -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type RuntimeTask = RuntimeTask; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = H160; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type ExtensionsWeightInfo = (); - type SingleBlockMigrations = (); - type MultiBlockMigrator = (); - type PreInherents = (); - type PostInherents = (); - type PostTransactions = (); -} - -parameter_types! { - pub const ExistentialDeposit: Balance = 1; -} - -impl pallet_balances::Config for Test { - type RuntimeEvent = RuntimeEvent; - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; - type WeightInfo = (); - type Balance = Balance; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type ReserveIdentifier = [u8; 8]; - type FreezeIdentifier = RuntimeFreezeReason; - type MaxLocks = (); - type MaxReserves = (); - type MaxFreezes = (); - type DoneSlashHandler = (); -} - -parameter_types! { - pub const MinimumPeriod: u64 = 1000; -} - -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -pub struct FixedGasPrice; -impl FeeCalculator for FixedGasPrice { - fn min_gas_price() -> (U256, Weight) { - (1_000_000_000u128.into(), Weight::from_parts(7u64, 0)) - } -} - -parameter_types! { - pub BlockGasLimit: U256 = U256::max_value(); - pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); -} - -impl pallet_evm::Config for Test { - type AccountProvider = pallet_evm::FrameSystemAccountProvider; - type FeeCalculator = FixedGasPrice; - type GasWeightMapping = pallet_evm::FixedGasWeightMapping; - type WeightPerGas = WeightPerGas; - type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; - type CallOrigin = EnsureAddressRoot; - type WithdrawOrigin = EnsureAddressNever; - type AddressMapping = IdentityAddressMapping; - type Currency = Balances; - type PrecompilesType = (); - type PrecompilesValue = (); - type ChainId = (); - type BlockGasLimit = BlockGasLimit; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = (); - type OnCreate = (); - type FindAuthor = (); - type GasLimitPovSizeRatio = (); - type GasLimitStorageGrowthRatio = (); - type Timestamp = Timestamp; - type CreateInnerOriginFilter = (); - type CreateOriginFilter = (); - type WeightInfo = (); -} - -/// Identity converter: each substrate H160 account maps to itself as EVM address. -pub struct IdentityEvmAddress; -impl Convert> for IdentityEvmAddress { - fn convert(account: H160) -> Option { - Some(account) - } -} - -parameter_types! { - pub const TestAliasDeposit: Balance = 100; - pub const TestMaxAliasLength: u32 = 32; -} - -/// Accepts a proof if its first byte is `0x01`. -pub struct MockPrivateLinkVerifier; -impl PrivateLinkVerifierPort for MockPrivateLinkVerifier { - fn verify(_commitment: &[u8; 32], _call_hash: &[u8; 32], proof: &[u8]) -> bool { - proof.first().copied() == Some(0x01) - } -} - -impl pallet_account_mapping::Config for Test { - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type AccountIdToEvmAddress = IdentityEvmAddress; - type AliasDeposit = TestAliasDeposit; - type MaxAliasLength = TestMaxAliasLength; - type WeightInfo = pallet_account_mapping::weights::SubstrateWeight; - type PrivateLinkVerifier = MockPrivateLinkVerifier; - type NativeEvmChainId = frame_support::traits::ConstU32<1>; // test chain id -} - -/// Caller address used in tests: `0x0000…0001`. -pub fn caller() -> H160 { - H160::from_low_u64_be(1) -} - -/// Builds test externalities and funds `caller()` with 1 000 000 units. -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![(caller(), 1_000_000)], - dev_accounts: None, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -// ───────────────────────────────────────────────────────────────────────────── -// MockHandle -// ───────────────────────────────────────────────────────────────────────────── - -pub(crate) struct MockHandle { - pub input: Vec, - pub context: Context, -} - -impl MockHandle { - pub fn new(input: Vec) -> Self { - Self { - input, - context: Context { - address: H160::zero(), - caller: caller(), - apparent_value: U256::zero(), - }, - } - } -} - -impl PrecompileHandle for MockHandle { - fn call( - &mut self, - _: H160, - _: Option, - _: Vec, - _: Option, - _: bool, - _: &Context, - ) -> (ExitReason, Vec) { - unimplemented!() - } - - fn record_cost(&mut self, _: u64) -> Result<(), ExitError> { - Ok(()) - } - - fn record_external_cost( - &mut self, - _ref_time: Option, - _proof_size: Option, - _storage_growth: Option, - ) -> Result<(), ExitError> { - Ok(()) - } - - fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} - - fn remaining_gas(&self) -> u64 { - u64::MAX - } - - fn log(&mut self, _: H160, _: Vec, _: Vec) -> Result<(), ExitError> { - Ok(()) - } - - fn code_address(&self) -> H160 { - H160::zero() - } - - fn input(&self) -> &[u8] { - &self.input - } - - fn context(&self) -> &Context { - &self.context - } - - fn origin(&self) -> H160 { - self.context.caller - } - - fn is_static(&self) -> bool { - false - } - - fn gas_limit(&self) -> Option { - None - } - - fn is_contract_being_constructed(&self, _address: H160) -> bool { - false - } -} diff --git a/frame/evm/precompile/account-mapping/src/tests.rs b/frame/evm/precompile/account-mapping/src/tests.rs deleted file mode 100644 index 0cc23817..00000000 --- a/frame/evm/precompile/account-mapping/src/tests.rs +++ /dev/null @@ -1,798 +0,0 @@ -//! Unit tests for `pallet-evm-precompile-account-mapping`. -//! -//! Each test exercises the precompile through `MockHandle` so that no EVM runner -//! is involved. Storage interactions use the in-memory `TestExternalities` built -//! by `mock::new_test_ext()`. - -use fp_evm::{ExitError, ExitSucceed, Precompile, PrecompileFailure}; -use sp_core::H160; - -use crate::{ - mock::{new_test_ext, MockHandle}, - AccountMappingPrecompile, -}; - -// ───────────────────────────────────────────────────────────────────────────── -// Helpers -// ───────────────────────────────────────────────────────────────────────────── - -/// Builds ABI-encoded `string` / `bytes` argument (offset pointer + length + data). -/// The encoded slice is suitable for appending after a function selector. -/// -/// Layout (relative to the start of params, i.e. after the 4-byte selector): -/// [0..32] offset = 0x20 (string data starts at byte 32 of params) -/// [32..64] length -/// [64..] UTF-8 bytes zero-padded to the next 32-byte boundary -fn abi_encode_string(s: &[u8]) -> Vec { - let padded_len = (s.len() + 31) & !31; - let mut out = vec![0u8; 64 + padded_len]; - // offset = 0x20 = 32 - out[31] = 0x20; - // length - let len_bytes = (s.len() as u64).to_be_bytes(); - out[56..64].copy_from_slice(&len_bytes); - // data - out[64..64 + s.len()].copy_from_slice(s); - out -} - -/// Builds params for `hasPrivateLink(string alias, bytes32 commitment)`. -/// -/// ABI layout (after selector): -/// [0..32] offset to string = 0x40 (64) -/// [32..64] bytes32 commitment (static) -/// [64..96] string length -/// [96..] string bytes zero-padded to 32-byte boundary -fn abi_encode_has_private_link(alias: &[u8], commitment: &[u8; 32]) -> Vec { - let padded_len = (alias.len() + 31) & !31; - let mut params = vec![0u8; 96 + padded_len]; - // offset to string = 64 (0x40) - params[31] = 0x40; - // commitment at [32..64] - params[32..64].copy_from_slice(commitment); - // string length at [64..96] - let len_bytes = (alias.len() as u64).to_be_bytes(); - params[88..96].copy_from_slice(&len_bytes); - // string data - params[96..96 + alias.len()].copy_from_slice(alias); - params -} - -// ───────────────────────────────────────────────────────────────────────────── -// Selector routing -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn selector_too_short_returns_error() { - new_test_ext().execute_with(|| { - // Only 3 bytes ─ selector requires at least 4 - let mut handle = MockHandle::new(vec![0xd0, 0x31, 0x49]); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - matches!( - result, - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(_) - }) - ), - "expected error for short input" - ); - }); -} - -#[test] -fn unknown_selector_returns_error() { - new_test_ext().execute_with(|| { - let mut handle = MockHandle::new(vec![0xff, 0xff, 0xff, 0xff]); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - matches!( - result, - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(_) - }) - ), - "expected error for unknown selector" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Read-only: resolveAlias -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn resolve_alias_not_found_returns_zero_addresses() { - new_test_ext().execute_with(|| { - // resolveAlias("noexist") - let mut input = vec![0xd0, 0x31, 0x49, 0xab]; - input.extend_from_slice(&abi_encode_string(b"noexist")); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - let out = result.expect("resolveAlias must succeed"); - assert_eq!(out.exit_status, ExitSucceed::Returned); - // Two H160 zero addresses → 64 zero bytes - assert_eq!(out.output.len(), 64); - assert_eq!(out.output, vec![0u8; 64]); - }); -} - -#[test] -fn resolve_alias_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - // Selector only — no ABI params at all - let mut handle = MockHandle::new(vec![0xd0, 0x31, 0x49, 0xab]); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!(result.is_err(), "resolveAlias with no params must fail"); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Read-only: getAliasOf -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn get_alias_of_no_alias_returns_empty_bytes() { - new_test_ext().execute_with(|| { - // getAliasOf(H160::zero()) ─ address not registered - let mut input = vec![0x7a, 0x0e, 0xd6, 0x2c]; - input.extend_from_slice(&[0u8; 32]); // zero-padded H160 - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - let out = result.expect("getAliasOf must succeed"); - assert_eq!(out.exit_status, ExitSucceed::Returned); - assert_eq!(out.output.len(), 64, "empty ABI bytes encoding is 64 bytes"); - // Offset slot = 0x20 - assert_eq!(out.output[31], 0x20); - // Length = 0 - let length = u64::from_be_bytes(out.output[56..64].try_into().unwrap()); - assert_eq!(length, 0); - }); -} - -#[test] -fn get_alias_of_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - // Only 20 bytes of params instead of 32 - let mut input = vec![0x7a, 0x0e, 0xd6, 0x2c]; - input.extend_from_slice(&[0u8; 20]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!(result.is_err(), "getAliasOf with short params must fail"); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Read-only: hasPrivateLink -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn has_private_link_returns_false_when_not_registered() { - new_test_ext().execute_with(|| { - // hasPrivateLink("alice", [0;32]) - let commitment = [0u8; 32]; - let mut input = vec![0x47, 0xe0, 0x5c, 0x6c]; - input.extend_from_slice(&abi_encode_has_private_link(b"alice", &commitment)); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - let out = result.expect("hasPrivateLink must succeed"); - assert_eq!(out.exit_status, ExitSucceed::Returned); - assert_eq!(out.output.len(), 32); - assert_eq!(out.output[31], 0, "expected false"); - }); -} - -#[test] -fn has_private_link_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - // Only 30 bytes after selector → fails "hasPrivateLink: input too short" - let mut input = vec![0x47, 0xe0, 0x5c, 0x6c]; - input.extend_from_slice(&[0u8; 30]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - result.is_err(), - "hasPrivateLink with short params must fail" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// State-changing: mapAccount -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn map_account_dispatches_call() { - new_test_ext().execute_with(|| { - // mapAccount() — no ABI params, just the 4-byte selector - let mut handle = MockHandle::new(vec![0xdc, 0xa4, 0x9d, 0x0e]); - let result = AccountMappingPrecompile::::execute(&mut handle); - - // The precompile must reach the dispatch stage; it must NOT fail with an - // ABI decode or routing error. - let failed_abi = match &result { - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(msg), - }) => { - msg.starts_with("input too short") - || msg.starts_with("ABI:") - || msg.starts_with("unknown selector") - } - _ => false, - }; - assert!( - !failed_abi, - "map_account should not fail with ABI/selector error" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// State-changing: registerAlias -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn register_alias_ok() { - new_test_ext().execute_with(|| { - // registerAlias("alice") — caller has 1_000_000 ≥ AliasDeposit (100) - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; - input.extend_from_slice(&abi_encode_string(b"alice")); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - assert!( - result.is_ok(), - "registerAlias with funded account must succeed: {result:?}" - ); - assert_eq!(result.unwrap().exit_status, ExitSucceed::Stopped); - }); -} - -#[test] -fn register_alias_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - // Only 10 bytes after selector — ABI slot occupies 32 bytes minimum - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; - input.extend_from_slice(&[0u8; 10]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - assert!( - result.is_err(), - "registerAlias with truncated ABI must fail" - ); - }); -} - -#[test] -fn register_alias_then_resolve_returns_correct_address() { - new_test_ext().execute_with(|| { - let caller_h160 = crate::mock::caller(); - - // 1. Register "bob" for caller() - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; - input.extend_from_slice(&abi_encode_string(b"bob")); - let mut handle = MockHandle::new(input); - AccountMappingPrecompile::::execute(&mut handle) - .expect("registerAlias must succeed"); - - // 2. Resolve "bob" - let mut input = vec![0xd0, 0x31, 0x49, 0xab]; - input.extend_from_slice(&abi_encode_string(b"bob")); - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("resolveAlias must succeed"); - - // First 32-byte slot encodes the owner H160 (bytes 12..32) - let resolved = H160::from_slice(&out.output[12..32]); - assert_eq!( - resolved, caller_h160, - "resolved address must match the registrant" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// State-changing: registerPrivateLink / removePrivateLink -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn register_private_link_ok() { - new_test_ext().execute_with(|| { - // First register an alias (required by the pallet) - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; - input.extend_from_slice(&abi_encode_string(b"link_test")); - let mut handle = MockHandle::new(input); - AccountMappingPrecompile::::execute(&mut handle) - .expect("registerAlias must succeed"); - - // registerPrivateLink(chainId=1, commitment=[0;32]) - let mut input = vec![0xc0, 0x4e, 0x98, 0xf4]; - let mut params = vec![0u8; 64]; - // chainId = 1 in last 4 bytes of first slot - params[28..32].copy_from_slice(&1u32.to_be_bytes()); - // commitment = [0;32] (already zero) - input.extend_from_slice(¶ms); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - assert!( - result.is_ok(), - "registerPrivateLink must succeed: {result:?}" - ); - }); -} - -#[test] -fn remove_private_link_ok() { - new_test_ext().execute_with(|| { - let commitment = [0u8; 32]; - - // 0. Register an alias first (required by the pallet) - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; - input.extend_from_slice(&abi_encode_string(b"rm_link_test")); - let mut handle = MockHandle::new(input); - AccountMappingPrecompile::::execute(&mut handle) - .expect("registerAlias must succeed"); - - // 1. Register the private link - let mut input = vec![0xc0, 0x4e, 0x98, 0xf4]; - let mut params = vec![0u8; 64]; - params[28..32].copy_from_slice(&1u32.to_be_bytes()); - input.extend_from_slice(¶ms); - let mut handle = MockHandle::new(input); - AccountMappingPrecompile::::execute(&mut handle) - .expect("registerPrivateLink must succeed"); - - // 2. Now remove it - let mut input = vec![0xdf, 0xd8, 0xb5, 0x7e]; - input.extend_from_slice(&commitment); - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - assert!(result.is_ok(), "removePrivateLink must succeed: {result:?}"); - }); -} - -#[test] -fn remove_private_link_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - // 20 bytes after selector instead of 32 - let mut input = vec![0xdf, 0xd8, 0xb5, 0x7e]; - input.extend_from_slice(&[0u8; 20]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - result.is_err(), - "removePrivateLink with short params must fail" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// State-changing: putAliasOnSale — price zero is rejected by pallet -// ───────────────────────────────────────────────────────────────────────────── - -#[test] -fn put_alias_on_sale_no_alias_returns_dispatch_error() { - new_test_ext().execute_with(|| { - // putAliasOnSale(price=1, allowedBuyers=[]) without having an alias first - let mut input = vec![0x32, 0x09, 0x11, 0x92]; - let mut params = vec![0u8; 96]; - // price = 1 - params[31] = 0x01; - // offset to address[] = 0x40 = 64 - params[63] = 0x40; - // array length = 0 (at params[64..96]) - input.extend_from_slice(¶ms); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - // Must fail with a dispatch error (caller has no alias), NOT an ABI error - match &result { - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(msg), - }) => { - let is_abi_error = msg.starts_with("input too short") - || msg.starts_with("ABI:") - || msg.starts_with("unknown selector"); - assert!( - !is_abi_error, - "expected dispatch error, got ABI error: {msg}" - ); - } - Ok(_) => panic!("expected dispatch error, but got Ok"), - Err(other) => panic!("unexpected failure variant: {other:?}"), - } - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Phase 3: composability helpers — isEvmSuffixAccount -// ───────────────────────────────────────────────────────────────────────────── - -/// bytes32 whose last 12 bytes are zero → suffix pattern → true -#[test] -fn is_evm_suffix_account_returns_true_for_suffix_pattern() { - new_test_ext().execute_with(|| { - // isEvmSuffixAccount(bytes32) selector: 0x96e69f8a - let mut input = vec![0x96, 0xe6, 0x9f, 0x8a]; - let mut bytes32 = [0u8; 32]; - // First 20 bytes = some H160; last 12 stay zero (suffix pattern). - bytes32[..20].copy_from_slice(H160::from_low_u64_be(0xdeadbeef).as_bytes()); - input.extend_from_slice(&bytes32); - - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("isEvmSuffixAccount must succeed"); - - assert_eq!(out.exit_status, ExitSucceed::Returned); - assert_eq!(out.output.len(), 32); - assert_eq!( - out.output[31], 1, - "last 12 bytes are zero → isEvmSuffix = true" - ); - }); -} - -/// bytes32 with a non-zero byte in positions 20..32 → not suffix → false -#[test] -fn is_evm_suffix_account_returns_false_for_non_suffix() { - new_test_ext().execute_with(|| { - let mut input = vec![0x96, 0xe6, 0x9f, 0x8a]; - let mut bytes32 = [0u8; 32]; - bytes32[20] = 0x01; // Sr25519 account: non-zero suffix byte - input.extend_from_slice(&bytes32); - - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("isEvmSuffixAccount must succeed"); - - assert_eq!( - out.output[31], 0, - "non-zero suffix byte → isEvmSuffix = false" - ); - }); -} - -/// Input shorter than selector + bytes32 must return an error. -#[test] -fn is_evm_suffix_account_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - let mut input = vec![0x96, 0xe6, 0x9f, 0x8a]; - input.extend_from_slice(&[0u8; 20]); // only 20 bytes instead of 32 - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - result.is_err(), - "isEvmSuffixAccount with short input must fail" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Phase 3: composability helpers — toEvmAddress -// ───────────────────────────────────────────────────────────────────────────── - -/// suffix bytes32 → derives H160 from first 20 bytes (no storage lookup) -#[test] -fn to_evm_address_derives_h160_from_suffix_bytes32() { - new_test_ext().execute_with(|| { - // toEvmAddress(bytes32) selector: 0x0f5b3052 - let target = H160::from_low_u64_be(0xcafe); - - let mut input = vec![0x0f, 0x5b, 0x30, 0x52]; - let mut bytes32 = [0u8; 32]; - bytes32[..20].copy_from_slice(target.as_bytes()); // last 12 bytes stay zero - input.extend_from_slice(&bytes32); - - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("toEvmAddress must succeed"); - - assert_eq!(out.exit_status, ExitSucceed::Returned); - assert_eq!(out.output.len(), 32); - // ABI address encoding: 12 zero bytes + 20 H160 bytes - assert_eq!(&out.output[0..12], &[0u8; 12]); - let derived = H160::from_slice(&out.output[12..32]); - assert_eq!( - derived, target, - "derived H160 must match first 20 bytes of suffix bytes32" - ); - }); -} - -/// Non-suffix bytes32 (Sr25519-style) → returns address(0) -#[test] -fn to_evm_address_returns_zero_for_non_suffix() { - new_test_ext().execute_with(|| { - let mut input = vec![0x0f, 0x5b, 0x30, 0x52]; - let mut bytes32 = [0u8; 32]; - bytes32[20] = 0x01; // non-zero suffix byte - input.extend_from_slice(&bytes32); - - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("toEvmAddress must succeed"); - - let derived = H160::from_slice(&out.output[12..32]); - assert_eq!( - derived, - H160::zero(), - "non-suffix bytes32 must return address(0)" - ); - }); -} - -/// Input shorter than selector + bytes32 must return an error. -#[test] -fn to_evm_address_abi_too_short_returns_error() { - new_test_ext().execute_with(|| { - let mut input = vec![0x0f, 0x5b, 0x30, 0x52]; - input.extend_from_slice(&[0u8; 20]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!(result.is_err(), "toEvmAddress with short input must fail"); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Phase 3: composability helpers — resolveAliasFull -// ───────────────────────────────────────────────────────────────────────────── - -/// Unknown alias → 96 zero bytes -#[test] -fn resolve_alias_full_returns_zeros_for_unknown_alias() { - new_test_ext().execute_with(|| { - // resolveAliasFull(string) selector: 0x2e40772b - let mut input = vec![0x2e, 0x40, 0x77, 0x2b]; - input.extend_from_slice(&abi_encode_string(b"no_such_alias")); - - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("resolveAliasFull must succeed even for unknown alias"); - - assert_eq!(out.exit_status, ExitSucceed::Returned); - assert_eq!(out.output.len(), 96); - assert_eq!( - out.output, - vec![0u8; 96], - "unknown alias must return 96 zero bytes" - ); - }); -} - -/// Registered alias → correct ABI layout (bytes32 zeros, evmAddress, isEvmSuffix=false) -/// -/// In the precompile mock, `is_implicit_evm_account` always returns false (default), -/// so the bytes32 accountId32 slot stays zero and evmAddress comes from storage. -#[test] -fn resolve_alias_full_returns_correct_layout_for_registered_alias() { - new_test_ext().execute_with(|| { - let caller_h160 = crate::mock::caller(); // H160::from_low_u64_be(1) - - // Register alias "full_test" for caller() - let mut input = vec![0x2f, 0x88, 0x39, 0xc3]; // registerAlias selector - input.extend_from_slice(&abi_encode_string(b"full_test")); - let mut handle = MockHandle::new(input); - AccountMappingPrecompile::::execute(&mut handle) - .expect("registerAlias must succeed"); - - // resolveAliasFull("full_test") - let mut input = vec![0x2e, 0x40, 0x77, 0x2b]; - input.extend_from_slice(&abi_encode_string(b"full_test")); - let mut handle = MockHandle::new(input); - let out = AccountMappingPrecompile::::execute(&mut handle) - .expect("resolveAliasFull must succeed"); - - assert_eq!(out.output.len(), 96); - // [0..32]: bytes32 accountId32 — zeros (is_suffix = false in mock) - assert_eq!( - &out.output[0..32], - &[0u8; 32], - "accountId32 slot must be zeros for non-suffix account" - ); - // [32..64]: address evmAddress — ABI-encoded H160 (12 zero bytes + 20 address bytes) - let resolved_evm = H160::from_slice(&out.output[44..64]); - assert_eq!( - resolved_evm, caller_h160, - "evmAddress slot must match caller()" - ); - // [64..96]: bool isEvmSuffix — false - assert_eq!( - out.output[95], 0, - "isEvmSuffix must be false for mock accounts" - ); - }); -} - -// ───────────────────────────────────────────────────────────────────────────── -// Phase 4: relay via chain link — dispatchAsLinkedAccount -// ───────────────────────────────────────────────────────────────────────────── - -/// Builds ABI-encoded params for `dispatchAsLinkedAccount(bytes32,uint32,bytes,bytes,bytes)`. -/// -/// Returns the params WITHOUT the 4-byte selector. -/// -/// ABI head layout (160 bytes): -/// [0..32] bytes32 owner (static) -/// [32..64] uint32 chainId (static, value in [60..64]) -/// [64..96] uint256 offset → bytes address (dynamic) -/// [96..128] uint256 offset → bytes signature (dynamic) -/// [128..160] uint256 offset → bytes call (dynamic) -fn abi_encode_dispatch_as_linked( - owner: &[u8; 32], - chain_id: u32, - address: &[u8], - signature: &[u8], - call: &[u8], -) -> Vec { - let addr_padded_len = (address.len() + 31) & !31; - let sig_padded_len = (signature.len() + 31) & !31; - let call_padded_len = (call.len() + 31) & !31; - - // Offsets are from the start of params (after selector). - let head: u64 = 160; - let addr_offset: u64 = head; - let sig_offset: u64 = addr_offset + 32 + addr_padded_len as u64; - let call_offset: u64 = sig_offset + 32 + sig_padded_len as u64; - - let total = head as usize + 32 + addr_padded_len + 32 + sig_padded_len + 32 + call_padded_len; - let mut params = vec![0u8; total]; - - // bytes32 owner (static) - params[0..32].copy_from_slice(owner); - // uint32 chainId (value in last 4 bytes of its 32-byte slot at [32..64]) - params[60..64].copy_from_slice(&chain_id.to_be_bytes()); - // offset to address (slot at [64..96], value in last 8 bytes) - params[88..96].copy_from_slice(&addr_offset.to_be_bytes()); - // offset to signature (slot at [96..128]) - params[120..128].copy_from_slice(&sig_offset.to_be_bytes()); - // offset to call (slot at [128..160]) - params[152..160].copy_from_slice(&call_offset.to_be_bytes()); - - // Dynamic: bytes address - let p = addr_offset as usize; - params[p + 24..p + 32].copy_from_slice(&(address.len() as u64).to_be_bytes()); - params[p + 32..p + 32 + address.len()].copy_from_slice(address); - - // Dynamic: bytes signature - let p = sig_offset as usize; - params[p + 24..p + 32].copy_from_slice(&(signature.len() as u64).to_be_bytes()); - params[p + 32..p + 32 + signature.len()].copy_from_slice(signature); - - // Dynamic: bytes call - let p = call_offset as usize; - params[p + 24..p + 32].copy_from_slice(&(call.len() as u64).to_be_bytes()); - params[p + 32..p + 32 + call.len()].copy_from_slice(call); - - params -} - -/// Input shorter than 5 ABI slots (160 bytes) must return an error. -#[test] -fn dispatch_as_linked_account_short_input_returns_error() { - new_test_ext().execute_with(|| { - // selector + 32 bytes (only 1 slot, need 5) - let mut input = vec![0x06, 0x30, 0xce, 0xf9]; - input.extend_from_slice(&[0u8; 32]); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - matches!( - result, - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(_) - }) - ), - "short input must return error" - ); - }); -} - -/// Params with correct ABI head but garbage SCALE bytes for `call` must return a decode error. -/// -/// Note: `0xFF` is an invalid pallet index in the mock runtime (only 0–4 are valid), -/// so `RuntimeCall::decode` will fail. -#[test] -fn dispatch_as_linked_account_invalid_scale_call_returns_error() { - new_test_ext().execute_with(|| { - // bytes32 owner: H160::from_low_u64_be(1) in first 20 bytes - let mut owner_slot = [0u8; 32]; - owner_slot[19] = 0x01; - - let params = abi_encode_dispatch_as_linked( - &owner_slot, - 2, // chain_id != NativeEvmChainId(1) in mock - b"external_addr", - b"fake_sig", - &[0xFF, 0xFF, 0xFF], // invalid SCALE: pallet index 0xFF does not exist - ); - - let mut input = vec![0x06, 0x30, 0xce, 0xf9]; - input.extend_from_slice(¶ms); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - assert!( - matches!( - result, - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(ref msg), - }) if msg.contains("SCALE") - ), - "invalid SCALE call must fail with SCALE decode error; got: {result:?}" - ); - }); -} - -/// Valid ABI encoding must route to the pallet (not fail with ABI/selector error). -/// -/// The pallet will return a chain-link error (no link registered), but the precompile -/// must not fail at the ABI decode stage. The mock NativeEvmChainId is 1, so we pass -/// chain_id = 2 to bypass the `UseNativeSignatureForEvmAccounts` guard. -#[test] -fn dispatch_as_linked_account_routes_to_pallet_with_valid_abi() { - use scale_codec::Encode; - - new_test_ext().execute_with(|| { - // bytes32 owner: H160::from_low_u64_be(1) — SCALE H160 = 20 bytes; first 20 of slot - let mut owner_slot = [0u8; 32]; - owner_slot[19] = 0x01; - - // SCALE-encode System::remark([]) as a valid RuntimeCall - let inner_call = - crate::mock::RuntimeCall::System(frame_system::Call::::remark { - remark: vec![], - }); - let call_bytes = inner_call.encode(); - - let params = abi_encode_dispatch_as_linked( - &owner_slot, - 2, // chain_id = 2; mock NativeEvmChainId = 1 (not blocked) - b"external_addr", - b"fake_sig", - &call_bytes, - ); - - let mut input = vec![0x06, 0x30, 0xce, 0xf9]; - input.extend_from_slice(¶ms); - - let mut handle = MockHandle::new(input); - let result = AccountMappingPrecompile::::execute(&mut handle); - - // ABI and routing must succeed — any pallet-level error is acceptable - // (no chain link is registered, so the pallet will reject the call). - let is_abi_error = match &result { - Err(PrecompileFailure::Error { - exit_status: ExitError::Other(msg), - }) => { - msg.starts_with("dispatchAsLinkedAccount: input too short") - || msg.starts_with("dispatchAsLinkedAccount: invalid owner") - || msg.starts_with("dispatchAsLinkedAccount: invalid SCALE") - || msg.starts_with("unknown selector") - } - _ => false, - }; - assert!( - !is_abi_error, - "valid ABI must not fail at the ABI decode stage; got: {result:?}" - ); - }); -} diff --git a/frame/evm/precompile/shielded-pool/src/mock.rs b/frame/evm/precompile/shielded-pool/src/mock.rs index 86cac396..93aef6c9 100644 --- a/frame/evm/precompile/shielded-pool/src/mock.rs +++ b/frame/evm/precompile/shielded-pool/src/mock.rs @@ -175,18 +175,6 @@ impl ZkVerifierPort for MockZkVerifier { Ok(true) } - fn verify_private_link_proof( - proof: &[u8], - _commitment: &[u8; 32], - _call_hash_fe: &[u8; 32], - _version: Option, - ) -> Result { - if proof.is_empty() { - return Err(sp_runtime::DispatchError::Other("Empty proof")); - } - Ok(true) - } - fn is_supported_version(_circuit_id: u32, version: u32) -> bool { version != 0 } diff --git a/frame/shielded-pool/src/merkle.rs b/frame/shielded-pool/src/merkle.rs index 8115bc0c..cf6544e3 100644 --- a/frame/shielded-pool/src/merkle.rs +++ b/frame/shielded-pool/src/merkle.rs @@ -1239,6 +1239,30 @@ mod tests { }); } + /// Mirrors the sealed-tree spend E2E: a note in tree 0 must still verify + /// against the sealed root after enough later inserts to rotate the whole + /// historic ring. + #[test] + fn sealed_tree_leaf_verifies_after_ring_rotation() { + use crate::storage::MerkleRepository; + new_test_ext().execute_with(|| { + let target = Commitment::new([0x9Au8; 32]); + MerkleTreeService::insert_leaf::(target).unwrap(); + for i in 0..120u32 { + let mut leaf = [0u8; 32]; + leaf[..4].copy_from_slice(&i.to_le_bytes()); + leaf[31] = 0x5A; + MerkleTreeService::insert_leaf::(Commitment::new(leaf)).unwrap(); + } + let sealed = MerkleRepository::get_sealed_root::(0).expect("tree 0 sealed"); + let path = MerkleTreeService::get_merkle_path::(0).expect("path for leaf 0"); + assert!( + MerkleTreeService::verify_merkle_proof(&sealed, &target.0, &path), + "tree-0 leaf must verify against the sealed root after 120 later inserts" + ); + }); + } + #[test] fn multiple_rollovers_keep_every_tree_provable() { use crate::storage::MerkleRepository; diff --git a/frame/shielded-pool/src/mock.rs b/frame/shielded-pool/src/mock.rs index 0f416646..3085f8ba 100644 --- a/frame/shielded-pool/src/mock.rs +++ b/frame/shielded-pool/src/mock.rs @@ -117,20 +117,6 @@ impl ZkVerifierPort for MockZkVerifier { Ok(true) } - fn verify_private_link_proof( - proof: &[u8], - _commitment: &[u8; 32], - _call_hash_fe: &[u8; 32], - _version: Option, - ) -> Result { - // Validate basic format - if proof.is_empty() { - return Err(sp_runtime::DispatchError::Other("Empty proof")); - } - // Always return true for testing (bypass ZK verification) - Ok(true) - } - fn is_supported_version(_circuit_id: u32, version: u32) -> bool { version != 0 } diff --git a/frame/zk-verifier/CHANGELOG.md b/frame/zk-verifier/CHANGELOG.md index e1c4e0c5..81dcb6f8 100644 --- a/frame/zk-verifier/CHANGELOG.md +++ b/frame/zk-verifier/CHANGELOG.md @@ -4,6 +4,76 @@ All notable changes to this pallet are documented here. --- +## [Unreleased] + +--- + +## [0.10.0] - 2026-08-03 + +### Added + +- **`purge_circuit(circuit_id)`** (call index 7, Root) — erases every version of a + circuit the runtime no longer implements, clearing all five maps: + `VerificationKeys`, `VkHashes`, `VerificationStats`, `RetiredVersions` and + `ActiveCircuitVersion`. + + It fills a gap the other calls could not: `remove_verification_key` and + `retire_version` both refuse to touch a circuit's active version, which is what + keeps a live circuit from ending up with no key to verify against. That same + guard makes them unable to retire a circuit as a whole, since its last version + is by construction the active one. + + The guard here is inverted rather than removed: a circuit is purgeable **only** + when `expected_public_inputs` returns `None` for its id. Transfer (1), unshield + (2) and value_proof (6) are rejected with `CircuitStillInUse` for as long as + they remain compiled in, regardless of storage contents. Ids above `u8::MAX` + are rejected outright rather than truncated into that lookup, so a circuit + numbered past 255 cannot alias onto a live id. + + The call clears `ActiveCircuitVersion` instead of requiring it to be empty. + Requiring it would make the extrinsic unreachable: the first + `register_verification_key` for a circuit activates the version it registers, + and no extrinsic ever clears that entry — `set_active_version` only overwrites, + and `retire_version` and `remove_verification_key` both refuse the active + version. An id the runtime no longer knows has no verification route whatever + that entry says. + + New errors: `CircuitStillInUse`, `CircuitHasNoStorage`. New event: + `CircuitPurged { circuit_id, removed }`, where `removed` counts storage entries + across every map — not versions — so an indexer reconciling against its own + view gets the real figure even when the maps hold different version sets. The + weight is still charged per version, which is what the benchmark measures. + +- **`STORAGE_VERSION`** and a `migrations` module — the pallet had neither. + +- **`migrations::v1::MigrateToV1`** — drops the retired `private_link` circuit + (id 5) during the runtime upgrade, so chains carrying its key from an earlier + runtime need no manual call. The id is hardcoded rather than derived: this runs + once, only on chains still at storage v0, where 5 can only mean `private_link`. + + Without this the key would linger and stay visible — `get_all_circuit_versions` + iterates storage keys with no allowlist, so explorers kept listing a circuit + the runtime could not serve. + +### Fixed + +- **`remove_verification_key` no longer strands satellite entries.** It cleared + `VerificationKeys` and `RetiredVersions` but left `VkHashes` and + `VerificationStats` behind, so a removed version kept a hash and a stats row no + call could reach. That also skewed the version count `store_vk` uses to enforce + `MAX_VERSIONS_PER_CIRCUIT`. All four maps are now cleared together. + +### Note + +`weights.rs` was regenerated for the whole pallet on reference hardware +(Hetzner CCX33, AMD EPYC-Genoa, steps 50 / repeat 20), so `purge_circuit` is +measured under the same conditions as every other extrinsic in the file rather +than extrapolated. Model: `59.5µs + 10.75µs * v`, 9 + 4v reads, 3 + 4v writes. +At the 64-version cap the call stays well under 10% of a 2000ms block, asserted +by `purge_circuit_at_the_cap_fits_in_a_block`. + +--- + ## [0.9.0] - 2026-07-09 ### Added diff --git a/frame/zk-verifier/Cargo.toml b/frame/zk-verifier/Cargo.toml index fb9432ad..449f0637 100644 --- a/frame/zk-verifier/Cargo.toml +++ b/frame/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-zk-verifier" -version = "0.9.0" +version = "0.10.0" description = "Zero-Knowledge proof verification pallet for Orbinum" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" diff --git a/frame/zk-verifier/README.md b/frame/zk-verifier/README.md index 1569ce77..d7e3c3bb 100644 --- a/frame/zk-verifier/README.md +++ b/frame/zk-verifier/README.md @@ -21,7 +21,6 @@ MVP in active development. Production runtime verifies Groth16 proofs on BN254. | `1` | transfer (2-in / 2-out UTXO) | | `2` | unshield (pool withdrawal) | | `3` | shield (reserved) | -| `5` | private_link | | `6` | value_proof (relay fee claiming) | ## Storage @@ -75,13 +74,6 @@ pub trait ZkVerifierPort { public_signals: &[u8], // exactly 76 bytes: commitment[0..32] | value[32..40] | asset_id[40..44] | owner_hash[44..76] version: Option, ) -> Result; - - fn verify_private_link_proof( - proof: &[u8], - commitment: &[u8; 32], - link_nullifier: &[u8; 32], - version: Option, - ) -> Result; } ``` diff --git a/frame/zk-verifier/src/benchmarking.rs b/frame/zk-verifier/src/benchmarking.rs index 25733935..d426fdec 100644 --- a/frame/zk-verifier/src/benchmarking.rs +++ b/frame/zk-verifier/src/benchmarking.rs @@ -243,6 +243,61 @@ mod benchmarks { assert!(!RetiredVersions::::contains_key(circuit_id, retired)); } + /// Uses circuit id 99 rather than the retired 5: the extrinsic only accepts + /// ids the runtime does not implement, and 99 stays unknown even if 5 is + /// ever reassigned. + /// + /// All four maps are seeded for every version, not just the two that + /// `register_verification_key` writes. The extrinsic iterates and clears each + /// one by prefix, so leaving `VerificationStats` and `RetiredVersions` empty + /// would measure half the work the weight has to cover. + #[benchmark] + fn purge_circuit(v: Linear<1, 64>) { + let circuit_id = CircuitId(99); + let registered_at = frame_system::Pallet::::block_number(); + + for version in 1..=v { + VerificationKeys::::insert( + circuit_id, + version, + VerificationKeyInfo { + key_data: sample_verification_key().try_into().unwrap(), + system: ProofSystem::Groth16, + registered_at, + }, + ); + VkHashes::::insert(circuit_id, version, [0u8; 32]); + VerificationStats::::insert( + circuit_id, + version, + crate::types::VerificationStatistics::default(), + ); + RetiredVersions::::insert(circuit_id, version, ()); + } + ActiveCircuitVersion::::insert(circuit_id, 1u32); + + #[extrinsic_call] + _(RawOrigin::Root, circuit_id); + + assert!( + VerificationKeys::::iter_key_prefix(circuit_id) + .next() + .is_none() + ); + assert!(VkHashes::::iter_key_prefix(circuit_id).next().is_none()); + assert!( + VerificationStats::::iter_key_prefix(circuit_id) + .next() + .is_none() + ); + assert!( + RetiredVersions::::iter_key_prefix(circuit_id) + .next() + .is_none() + ); + assert!(ActiveCircuitVersion::::get(circuit_id).is_none()); + } + #[benchmark] fn batch_register_verification_keys(n: Linear<1, 10>) { let vk_bytes = sample_verification_key(); diff --git a/frame/zk-verifier/src/encoding.rs b/frame/zk-verifier/src/encoding.rs index a9270ab0..b1e05ed8 100644 --- a/frame/zk-verifier/src/encoding.rs +++ b/frame/zk-verifier/src/encoding.rs @@ -69,13 +69,6 @@ pub fn encode_unshield( ] } -/// Encode private-link dispatch public inputs. -/// -/// Order: `commitment | call_hash_fe` -pub fn encode_private_link(commitment: &[u8; 32], call_hash_fe: &[u8; 32]) -> Vec<[u8; 32]> { - alloc::vec![*commitment, *call_hash_fe] -} - /// Encode value proof public inputs (CircuitId 6). /// /// Expands the compact 76-byte on-chain layout into 4 BN254 field elements: @@ -143,12 +136,6 @@ mod tests { assert_eq!(raw[3][31], 0xFF); } - #[test] - fn encode_private_link_correct_length() { - let raw = encode_private_link(&[1u8; 32], &[2u8; 32]); - assert_eq!(raw.len(), 2); - } - #[test] fn encode_value_proof_correct_length() { let signals = [0u8; 76]; diff --git a/frame/zk-verifier/src/lib.rs b/frame/zk-verifier/src/lib.rs index 1ebedd01..ed5dc8a1 100644 --- a/frame/zk-verifier/src/lib.rs +++ b/frame/zk-verifier/src/lib.rs @@ -22,6 +22,7 @@ extern crate alloc; pub use pallet::*; mod encoding; +pub mod migrations; mod port; mod runtime_api; mod types; @@ -50,7 +51,13 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// Storage version history: + /// - v1: drop the retired `private_link` circuit (id 5), cleared by + /// `migrations::v1::MigrateToV1`. + pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -178,14 +185,42 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - VerificationKeyRegistered { circuit_id: CircuitId, version: u32 }, - ActiveVersionSet { circuit_id: CircuitId, version: u32 }, - VerificationKeyRemoved { circuit_id: CircuitId, version: u32 }, - VersionRetired { circuit_id: CircuitId, version: u32 }, - VersionUnretired { circuit_id: CircuitId, version: u32 }, - ProofVerified { circuit_id: CircuitId, version: u32 }, - ProofVerificationFailed { circuit_id: CircuitId, version: u32 }, - BatchVerificationKeysRegistered { count: u32 }, + VerificationKeyRegistered { + circuit_id: CircuitId, + version: u32, + }, + ActiveVersionSet { + circuit_id: CircuitId, + version: u32, + }, + VerificationKeyRemoved { + circuit_id: CircuitId, + version: u32, + }, + VersionRetired { + circuit_id: CircuitId, + version: u32, + }, + VersionUnretired { + circuit_id: CircuitId, + version: u32, + }, + ProofVerified { + circuit_id: CircuitId, + version: u32, + }, + ProofVerificationFailed { + circuit_id: CircuitId, + version: u32, + }, + BatchVerificationKeysRegistered { + count: u32, + }, + /// Every version of a retired circuit was purged from storage. + CircuitPurged { + circuit_id: CircuitId, + removed: u32, + }, } // ── Errors ──────────────────────────────────────────────────────────────── @@ -225,6 +260,9 @@ pub mod pallet { InvalidBatchSize, BatchLengthMismatch, BatchVerificationFailed, + // Purge + CircuitStillInUse, + CircuitHasNoStorage, } // ── Extrinsics ──────────────────────────────────────────────────────────── @@ -326,6 +364,8 @@ pub mod pallet { VerificationKeys::::remove(circuit_id, version); RetiredVersions::::remove(circuit_id, version); + VkHashes::::remove(circuit_id, version); + VerificationStats::::remove(circuit_id, version); Self::deposit_event(Event::VerificationKeyRemoved { circuit_id, version, @@ -476,6 +516,98 @@ pub mod pallet { }); Ok(()) } + + /// Erase every trace of a circuit the runtime no longer implements. + /// + /// `remove_verification_key` and `retire_version` both refuse to touch the + /// active version, which is what keeps a live circuit from ending up with + /// no key to verify against. That guard also makes them unable to retire a + /// circuit as a whole: its last version is, by construction, the active one. + /// This call covers that gap — and only that gap. + /// + /// The one guard: the runtime must no longer know the id — + /// `expected_public_inputs` returns `None`. An id above `u8::MAX` is + /// rejected outright rather than truncated into that lookup, so a future + /// circuit numbered past 255 cannot alias its way past this check. + /// + /// `ActiveCircuitVersion` is cleared here rather than required to be empty + /// beforehand. Requiring it would make the call unreachable: the first + /// `register_verification_key` for a circuit activates the version it + /// registers, and no extrinsic ever clears that entry — `set_active_version` + /// only overwrites, and both `retire_version` and `remove_verification_key` + /// refuse to touch whichever version is active. An unknown id has no + /// verification route regardless of what the entry says, so it carries no + /// authority worth guarding. + /// + /// Clears all five maps by prefix — `VerificationKeys`, `VkHashes`, + /// `VerificationStats`, `RetiredVersions`, `ActiveCircuitVersion` — rather + /// than iterating one map's versions, so entries orphaned in the satellite + /// maps by an earlier `remove_verification_key` are collected too. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::purge_circuit(Pallet::::MAX_VERSIONS_PER_CIRCUIT))] + pub fn purge_circuit( + origin: OriginFor, + circuit_id: CircuitId, + ) -> DispatchResultWithPostInfo { + ensure_root(origin)?; + + // Fail closed on ids that do not fit the lookup: `as u8` would alias + // e.g. 257 onto 1, so an id past 255 must never reach the table. + let id = u8::try_from(circuit_id.0).map_err(|_| Error::::CircuitStillInUse)?; + ensure!( + orbinum_zk_verifier::expected_public_inputs(id).is_none(), + Error::::CircuitStillInUse + ); + + // Counted by iterating rather than from `clear_prefix`'s result: its + // counters only report keys committed to the backend, and everything + // written earlier in the same block still lives in the overlay. + // + // Each map is counted on its own and the totals summed, not maxed: the + // four normally share a version set, but nothing in the type system says + // they must, and the sum is what the clear below actually pays for. + let per_map = [ + VerificationKeys::::iter_key_prefix(circuit_id).count(), + VkHashes::::iter_key_prefix(circuit_id).count(), + VerificationStats::::iter_key_prefix(circuit_id).count(), + RetiredVersions::::iter_key_prefix(circuit_id).count(), + ]; + // A stale `ActiveCircuitVersion` with no versions behind it is still an + // entry to clear, so it belongs in the total the event reports. + let active_entries = usize::from(ActiveCircuitVersion::::contains_key(circuit_id)); + let entries = per_map.iter().sum::().saturating_add(active_entries); + + ensure!(entries > 0, Error::::CircuitHasNoStorage); + // The cap bounds versions per map, so the clear is bounded by the widest + // map, not by the total. More than that means storage was built outside + // `store_vk`; bail rather than clear part of it — the extrinsic reverts + // as a whole, so nothing is left half-done. + let widest = per_map.iter().copied().max().unwrap_or(0); + ensure!( + widest <= Self::MAX_VERSIONS_PER_CIRCUIT as usize, + Error::::TooManyVersions + ); + + // `u32::MAX` as the limit: the count above already proved the prefix + // fits within the cap, so this only has to clear everything in one pass. + let _ = VerificationKeys::::clear_prefix(circuit_id, u32::MAX, None); + let _ = VkHashes::::clear_prefix(circuit_id, u32::MAX, None); + let _ = VerificationStats::::clear_prefix(circuit_id, u32::MAX, None); + let _ = RetiredVersions::::clear_prefix(circuit_id, u32::MAX, None); + ActiveCircuitVersion::::remove(circuit_id); + + // Reports entries cleared across every map, not versions: an indexer + // reconciling against its own view needs the real figure, and a stale + // active pointer with no versions behind it still cleared something. + Self::deposit_event(Event::CircuitPurged { + circuit_id, + removed: entries as u32, + }); + // Weighed by versions, not entries: the benchmark's linear component + // charges one read and four writes per version, so it is already the + // per-version cost of clearing all four maps. + Ok(Some(T::WeightInfo::purge_circuit(widest as u32)).into()) + } } impl Pallet { @@ -1621,4 +1753,291 @@ mod tests { use frame_support::traits::Hooks; >>::integrity_test(); } + + // ── purge_circuit ───────────────────────────────────────────────────────── + + /// Seeds every map for `(circuit_id, version)` and marks the version active. + /// Callers that then purge must clear `ActiveCircuitVersion` first — the + /// extrinsic refuses circuits that still have one. + fn seed_circuit(circuit_id: CircuitId, version: u32, arity: usize) { + VerificationKeys::::insert( + circuit_id, + version, + VerificationKeyInfo { + key_data: real_vk(arity), + system: ProofSystem::Groth16, + registered_at: 0u64, + }, + ); + crate::pallet::VkHashes::::insert(circuit_id, version, [0x11u8; 32]); + ActiveCircuitVersion::::insert(circuit_id, version); + } + + #[test] + fn purge_circuit_clears_every_map() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + seed_circuit(retired, 1, 2); + RetiredVersions::::insert(retired, 1, ()); + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + assert!(VerificationKeys::::get(retired, 1).is_none()); + assert!(crate::pallet::VkHashes::::get(retired, 1).is_none()); + assert!(!crate::pallet::VerificationStats::::contains_key( + retired, 1 + )); + assert!(!RetiredVersions::::contains_key(retired, 1)); + assert!(ActiveCircuitVersion::::get(retired).is_none()); + }); + } + + /// The guard that makes this call safe: a circuit the runtime still + /// implements can never be purged, no matter what storage holds. + #[test] + fn purge_circuit_rejects_live_circuits() { + new_test_ext().execute_with(|| { + for cid in [ + CircuitId::TRANSFER, + CircuitId::UNSHIELD, + CircuitId::VALUE_PROOF, + ] { + seed_circuit(cid, 1, TRANSFER_PUBLIC_INPUTS); + assert_noop!( + ZkVerifier::purge_circuit(root().into(), cid), + Error::::CircuitStillInUse + ); + assert!(VerificationKeys::::get(cid, 1).is_some()); + } + }); + } + + /// `circuit_id.0 as u8` would alias 257 onto TRANSFER(1). Ids past `u8::MAX` + /// must be rejected outright rather than truncated into the lookup. + #[test] + fn purge_circuit_rejects_ids_above_u8_max() { + new_test_ext().execute_with(|| { + for id in [256u32, 257, 261, 262, u32::MAX] { + let cid = CircuitId(id); + seed_circuit(cid, 1, 2); + assert_noop!( + ZkVerifier::purge_circuit(root().into(), cid), + Error::::CircuitStillInUse + ); + assert!(VerificationKeys::::get(cid, 1).is_some()); + } + }); + } + + /// Purging must work on a circuit reached the only way an operator can reach + /// one: through the extrinsics. `register_verification_key` activates the + /// first version it stores and nothing else ever clears that pointer, so a + /// call that required `ActiveCircuitVersion` to be empty could never fire. + #[test] + fn purge_circuit_works_on_a_circuit_built_through_extrinsics() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + assert_ok!(ZkVerifier::register_verification_key( + root().into(), + retired, + 1, + real_vk(2) + )); + // The registration activated version 1, and no extrinsic can undo that: + // `retire_version` and `remove_verification_key` both refuse the active + // version, and `set_active_version` only ever overwrites. + assert_eq!(ActiveCircuitVersion::::get(retired), Some(1)); + assert_noop!( + ZkVerifier::retire_version(root().into(), retired, 1), + Error::::CannotRetireActiveVersion + ); + assert_noop!( + ZkVerifier::remove_verification_key(root().into(), retired, 1), + Error::::CannotRemoveActiveVersion + ); + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + assert!(VerificationKeys::::get(retired, 1).is_none()); + assert!(ActiveCircuitVersion::::get(retired).is_none()); + }); + } + + #[test] + fn purge_circuit_requires_root() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + seed_circuit(retired, 1, 2); + assert_err!( + ZkVerifier::purge_circuit(signed().into(), retired), + sp_runtime::DispatchError::BadOrigin + ); + assert!(VerificationKeys::::get(retired, 1).is_some()); + }); + } + + #[test] + fn purge_circuit_fails_when_nothing_stored() { + new_test_ext().execute_with(|| { + assert_noop!( + ZkVerifier::purge_circuit(root().into(), CircuitId(5)), + Error::::CircuitHasNoStorage + ); + }); + } + + /// The declared weight is charged upfront, so a purge at the version cap must + /// leave room for the rest of the block. The runtime allows 2s of compute and + /// this call currently costs ~33ms of it; the 10% bound is loose enough not to + /// break on a re-benchmark but tight enough to catch a coefficient that grows + /// by an order of magnitude. + #[test] + fn purge_circuit_at_the_cap_fits_in_a_block() { + use crate::weights::WeightInfo; + use frame_support::weights::constants::WEIGHT_REF_TIME_PER_MILLIS; + + let worst = + ::WeightInfo::purge_circuit(Pallet::::MAX_VERSIONS_PER_CIRCUIT); + // Matches MAXIMUM_BLOCK_WEIGHT in the runtime. + let block = 2_000u64 * WEIGHT_REF_TIME_PER_MILLIS; + assert!( + worst.ref_time() < block / 10, + "purge_circuit at the cap costs {}ms of a 2000ms block", + worst.ref_time() / WEIGHT_REF_TIME_PER_MILLIS + ); + } + + /// An active pointer with no versions behind it is the one thing left to + /// clear, so the "nothing stored" guard must not treat it as nothing — and + /// the event must not report it as having cleared nothing either. + #[test] + fn purge_circuit_clears_a_stranded_active_pointer() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + ActiveCircuitVersion::::insert(retired, 1); + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + assert!(ActiveCircuitVersion::::get(retired).is_none()); + assert!(has_event(Event::CircuitPurged { + circuit_id: retired, + removed: 1 + })); + }); + } + + /// `removed` counts entries across every map, not versions. Maps whose + /// version sets differ would under-report if the count were a max, leaving an + /// indexer reconciling against a figure smaller than what actually went away. + #[test] + fn purge_circuit_reports_entries_not_versions() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + // One version with a key + hash, plus an orphan hash at another version + // and a retired marker at a third: 2 + 1 + 1 entries, spread over three + // distinct versions, none of which is a max of the per-map counts. + seed_circuit(retired, 1, 2); + crate::pallet::VkHashes::::insert(retired, 2, [0x22u8; 32]); + RetiredVersions::::insert(retired, 3, ()); + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + // keys(1) + hashes(2) + stats(0) + retired(1) + active pointer(1) + assert!(has_event(Event::CircuitPurged { + circuit_id: retired, + removed: 5 + })); + }); + } + + #[test] + fn purge_circuit_removes_every_version() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + for version in 1..=3u32 { + seed_circuit(retired, version, 2); + } + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + assert!( + VerificationKeys::::iter_key_prefix(retired) + .next() + .is_none() + ); + }); + } + + /// An earlier `remove_verification_key` used to strand hashes and stats with + /// no `VerificationKeys` row to enumerate them. Clearing by prefix collects + /// them; iterating one map's versions would not. + #[test] + fn purge_circuit_clears_orphaned_satellite_entries() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + crate::pallet::VkHashes::::insert(retired, 1, [0x11u8; 32]); + crate::pallet::VerificationStats::::insert( + retired, + 1, + crate::types::VerificationStatistics::default(), + ); + RetiredVersions::::insert(retired, 1, ()); + // Deliberately no VerificationKeys entry. + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + assert!(crate::pallet::VkHashes::::get(retired, 1).is_none()); + assert!(!crate::pallet::VerificationStats::::contains_key( + retired, 1 + )); + assert!(!RetiredVersions::::contains_key(retired, 1)); + }); + } + + /// After a purge the circuit must be invisible to the runtime API, which is + /// what keeps explorers from listing a circuit the runtime cannot serve. + #[test] + fn purged_circuit_disappears_from_runtime_api() { + new_test_ext().execute_with(|| { + let retired = CircuitId(5); + seed_circuit(retired, 1, 2); + seed_circuit(CircuitId::TRANSFER, 1, TRANSFER_PUBLIC_INPUTS); + + assert_ok!(ZkVerifier::purge_circuit(root().into(), retired)); + + let ids: Vec = ZkVerifier::runtime_api_get_all_circuit_versions() + .into_iter() + .map(|info| info.circuit_id) + .collect(); + assert!(!ids.contains(&5)); + assert!(ids.contains(&CircuitId::TRANSFER.0)); + }); + } + + /// `remove_verification_key` must not strand the satellite maps either — + /// that stranding is what made the orphan case above possible. + #[test] + fn remove_vk_also_clears_hash_and_stats() { + new_test_ext().execute_with(|| { + let cid = CircuitId::TRANSFER; + seed_circuit(cid, 1, TRANSFER_PUBLIC_INPUTS); + seed_circuit(cid, 2, TRANSFER_PUBLIC_INPUTS); + ActiveCircuitVersion::::insert(cid, 1u32); + crate::pallet::VerificationStats::::insert( + cid, + 2, + crate::types::VerificationStatistics::default(), + ); + + assert_ok!(ZkVerifier::remove_verification_key(root().into(), cid, 2)); + + assert!(crate::pallet::VkHashes::::get(cid, 2).is_none()); + assert!(!crate::pallet::VerificationStats::::contains_key( + cid, 2 + )); + // The active version is untouched. + assert!(VerificationKeys::::get(cid, 1).is_some()); + assert!(crate::pallet::VkHashes::::get(cid, 1).is_some()); + }); + } } diff --git a/frame/zk-verifier/src/migrations.rs b/frame/zk-verifier/src/migrations.rs new file mode 100644 index 00000000..7018623b --- /dev/null +++ b/frame/zk-verifier/src/migrations.rs @@ -0,0 +1,327 @@ +//! Storage migrations for pallet-zk-verifier. + +use crate::pallet::{ + ActiveCircuitVersion, Config, Pallet, RetiredVersions, VerificationKeys, VerificationStats, + VkHashes, +}; +use crate::types::CircuitId; +use crate::weights::WeightInfo as _; +use frame_support::{ + pallet_prelude::*, + traits::{GetStorageVersion, OnRuntimeUpgrade}, + weights::Weight, +}; +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +pub mod v1 { + use super::*; + + /// Circuit id of the retired `private_link` proof. + /// + /// Hardcoded rather than derived from `expected_public_inputs`: this runs + /// once, only on chains still at storage v0, where 5 can only ever mean + /// `private_link`. A chain that reassigns the id is already past v1 and never + /// executes this. + const RETIRED_PRIVATE_LINK: CircuitId = CircuitId(5); + + /// Storage v0 -> v1: drop the `private_link` circuit (id 5). + /// + /// The circuit was removed from the runtime along with + /// `pallet-account-mapping`, its only consumer, but chains that ran the + /// previous runtime still carry its verification key on-chain. Nothing can + /// route to it — `ZkVerifierPort` no longer exposes + /// `verify_private_link_proof` — yet it stays visible: the + /// `get_all_circuit_versions` runtime API iterates storage keys with no + /// allowlist, so explorers keep listing a circuit the runtime cannot serve. + /// + /// Runs unconditionally rather than leaving the cleanup to `purge_circuit`: + /// the migration lands with the runtime that retires the circuit, so chains + /// are clean the moment they upgrade instead of waiting on a governance call. + /// + /// One-shot, and in practice the circuit only ever had one version registered. + pub struct MigrateToV1(core::marker::PhantomData); + + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + if Pallet::::on_chain_storage_version() >= 1 { + return T::DbWeight::get().reads(1); + } + + // Cleared by prefix, not by iterating one map's versions: an earlier + // `remove_verification_key` dropped keys without their hash or stats, so + // the satellite maps can hold entries no `VerificationKeys` row lists. + // + // Counted separately because `clear_prefix`'s own counters only report + // keys committed to the backend, and writes from the same block are + // still in the overlay. + // + // The widest map, not the sum: this feeds `purge_circuit`'s benchmark, + // whose linear component is already the per-version cost of clearing all + // four maps. + let versions = [ + VerificationKeys::::iter_key_prefix(RETIRED_PRIVATE_LINK).count(), + VkHashes::::iter_key_prefix(RETIRED_PRIVATE_LINK).count(), + VerificationStats::::iter_key_prefix(RETIRED_PRIVATE_LINK).count(), + RetiredVersions::::iter_key_prefix(RETIRED_PRIVATE_LINK).count(), + ] + .into_iter() + .max() + .unwrap_or(0) as u32; + + let _ = VerificationKeys::::clear_prefix(RETIRED_PRIVATE_LINK, u32::MAX, None); + let _ = VkHashes::::clear_prefix(RETIRED_PRIVATE_LINK, u32::MAX, None); + let _ = VerificationStats::::clear_prefix(RETIRED_PRIVATE_LINK, u32::MAX, None); + let _ = RetiredVersions::::clear_prefix(RETIRED_PRIVATE_LINK, u32::MAX, None); + ActiveCircuitVersion::::remove(RETIRED_PRIVATE_LINK); + + StorageVersion::new(1).put::>(); + + // Reuse the extrinsic's benchmark: it measures exactly this work, so the + // migration cannot drift from the cost the runner actually recorded. + T::WeightInfo::purge_circuit(versions) + .saturating_add(T::DbWeight::get().reads_writes(1, 2)) + } + + /// Records how many circuits are registered, so `post_upgrade` can assert + /// the migration removed at most the retired one. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let others = VerificationKeys::::iter_keys() + .filter(|(cid, _)| *cid != RETIRED_PRIVATE_LINK) + .count() as u32; + Ok(others.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let others_before = u32::decode(&mut state.as_slice()) + .map_err(|_| sp_runtime::TryRuntimeError::Other("pre_upgrade state decode"))?; + + frame_support::ensure!( + Pallet::::on_chain_storage_version() >= 1, + sp_runtime::TryRuntimeError::Other("storage version not bumped to 1") + ); + frame_support::ensure!( + VerificationKeys::::iter_key_prefix(RETIRED_PRIVATE_LINK) + .next() + .is_none(), + sp_runtime::TryRuntimeError::Other("circuit 5 keys still present") + ); + // The satellite maps are the ones an earlier `remove_verification_key` + // could strand, so check them explicitly rather than trusting that + // clearing `VerificationKeys` implied clearing these. + frame_support::ensure!( + VkHashes::::iter_key_prefix(RETIRED_PRIVATE_LINK) + .next() + .is_none(), + sp_runtime::TryRuntimeError::Other("circuit 5 vk hashes still present") + ); + frame_support::ensure!( + VerificationStats::::iter_key_prefix(RETIRED_PRIVATE_LINK) + .next() + .is_none(), + sp_runtime::TryRuntimeError::Other("circuit 5 stats still present") + ); + frame_support::ensure!( + RetiredVersions::::iter_key_prefix(RETIRED_PRIVATE_LINK) + .next() + .is_none(), + sp_runtime::TryRuntimeError::Other("circuit 5 retired versions still present") + ); + frame_support::ensure!( + ActiveCircuitVersion::::get(RETIRED_PRIVATE_LINK).is_none(), + sp_runtime::TryRuntimeError::Other("circuit 5 still has an active version") + ); + + // Every other circuit must be untouched: this migration only ever + // clears the retired id. + let others_after = VerificationKeys::::iter_keys() + .filter(|(cid, _)| *cid != RETIRED_PRIVATE_LINK) + .count() as u32; + frame_support::ensure!( + others_after == others_before, + sp_runtime::TryRuntimeError::Other("migration touched a live circuit") + ); + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::v1::MigrateToV1; + use crate::mock::Test; + use crate::pallet::{ + ActiveCircuitVersion, Pallet, RetiredVersions, VerificationKeys, VerificationStats, + VkHashes, + }; + use crate::types::{CircuitId, ProofSystem, VerificationKeyInfo}; + use frame_support::{ + BoundedVec, + traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + }; + use sp_io::TestExternalities; + use sp_runtime::BuildStorage; + + const PRIVATE_LINK: CircuitId = CircuitId(5); + + fn new_test_ext() -> TestExternalities { + let storage = frame_system::GenesisConfig::::default() + .build_storage() + .expect("mock storage ok"); + TestExternalities::new(storage) + } + + fn seed(circuit_id: CircuitId, version: u32) { + let key_data: BoundedVec> = + vec![0xABu8; 300].try_into().unwrap(); + VerificationKeys::::insert( + circuit_id, + version, + VerificationKeyInfo { + key_data, + system: ProofSystem::Groth16, + registered_at: 0u64, + }, + ); + VkHashes::::insert(circuit_id, version, [0x11u8; 32]); + ActiveCircuitVersion::::insert(circuit_id, version); + } + + #[test] + fn clears_every_map_for_the_retired_circuit() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + seed(PRIVATE_LINK, 1); + RetiredVersions::::insert(PRIVATE_LINK, 1, ()); + + MigrateToV1::::on_runtime_upgrade(); + + assert!(VerificationKeys::::get(PRIVATE_LINK, 1).is_none()); + assert!(VkHashes::::get(PRIVATE_LINK, 1).is_none()); + assert!(!VerificationStats::::contains_key(PRIVATE_LINK, 1)); + assert!(!RetiredVersions::::contains_key(PRIVATE_LINK, 1)); + assert!(ActiveCircuitVersion::::get(PRIVATE_LINK).is_none()); + assert_eq!(Pallet::::on_chain_storage_version(), 1); + }); + } + + /// The migration must not disturb circuits the runtime still implements. + #[test] + fn leaves_live_circuits_untouched() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + seed(CircuitId::TRANSFER, 1); + seed(CircuitId::UNSHIELD, 1); + seed(CircuitId::VALUE_PROOF, 1); + seed(PRIVATE_LINK, 1); + + MigrateToV1::::on_runtime_upgrade(); + + for cid in [ + CircuitId::TRANSFER, + CircuitId::UNSHIELD, + CircuitId::VALUE_PROOF, + ] { + assert!(VerificationKeys::::get(cid, 1).is_some()); + assert_eq!(ActiveCircuitVersion::::get(cid), Some(1)); + } + assert!(VerificationKeys::::get(PRIVATE_LINK, 1).is_none()); + }); + } + + /// The case the prefix-clear exists for: an earlier `remove_verification_key` + /// dropped the key but left its hash and stats, so there is no + /// `VerificationKeys` row to enumerate them from. + #[test] + fn clears_satellite_maps_with_no_verification_key() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + VkHashes::::insert(PRIVATE_LINK, 1, [0x11u8; 32]); + RetiredVersions::::insert(PRIVATE_LINK, 1, ()); + // Deliberately no VerificationKeys entry. + + MigrateToV1::::on_runtime_upgrade(); + + assert!(VkHashes::::get(PRIVATE_LINK, 1).is_none()); + assert!(!RetiredVersions::::contains_key(PRIVATE_LINK, 1)); + }); + } + + /// Running twice must be a no-op, not a double-charge. + #[test] + fn is_idempotent() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + seed(PRIVATE_LINK, 1); + + let first = MigrateToV1::::on_runtime_upgrade(); + let second = MigrateToV1::::on_runtime_upgrade(); + + // Second run hits the version guard and returns before doing any work, + // so it must be strictly cheaper than the run that cleared storage. + assert!(second.ref_time() < first.ref_time() || second.all_lte(first)); + assert!(VerificationKeys::::get(PRIVATE_LINK, 1).is_none()); + }); + } + + /// A chain that never registered the circuit still lands on version 1. + #[test] + fn bumps_version_when_nothing_to_clear() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + + MigrateToV1::::on_runtime_upgrade(); + + assert_eq!(Pallet::::on_chain_storage_version(), 1); + }); + } + + /// The `try-runtime` hooks are what an upgrade dry-run relies on, but nothing + /// else in the suite executes them: the tests above call `on_runtime_upgrade` + /// directly, and the CLI cannot get this far on a real snapshot because an + /// earlier migration in the tuple needs a host function it does not register. + /// Without this the checks are unrun code. + #[cfg(feature = "try-runtime")] + #[test] + fn try_runtime_hooks_pass_over_a_realistic_state() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + // The retired circuit alongside live ones, plus satellite entries the + // post-upgrade checks look at individually. + seed(PRIVATE_LINK, 1); + seed(PRIVATE_LINK, 2); + RetiredVersions::::insert(PRIVATE_LINK, 2, ()); + VerificationStats::::insert( + PRIVATE_LINK, + 1, + crate::types::VerificationStatistics::default(), + ); + seed(CircuitId::TRANSFER, 1); + seed(CircuitId::UNSHIELD, 1); + + let state = MigrateToV1::::pre_upgrade().expect("pre_upgrade"); + MigrateToV1::::on_runtime_upgrade(); + MigrateToV1::::post_upgrade(state).expect("post_upgrade"); + }); + } + + /// `post_upgrade` must fail, not pass silently, when the migration left the + /// circuit behind — otherwise a dry-run would green-light a broken upgrade. + #[cfg(feature = "try-runtime")] + #[test] + fn try_runtime_post_upgrade_catches_leftovers() { + new_test_ext().execute_with(|| { + StorageVersion::new(0).put::>(); + seed(PRIVATE_LINK, 1); + + let state = MigrateToV1::::pre_upgrade().expect("pre_upgrade"); + MigrateToV1::::on_runtime_upgrade(); + // Simulate a migration that missed one of the satellite maps. + VkHashes::::insert(PRIVATE_LINK, 1, [0x11u8; 32]); + + assert!(MigrateToV1::::post_upgrade(state).is_err()); + }); + } +} diff --git a/frame/zk-verifier/src/port.rs b/frame/zk-verifier/src/port.rs index 4065c975..2a5c1590 100644 --- a/frame/zk-verifier/src/port.rs +++ b/frame/zk-verifier/src/port.rs @@ -1,7 +1,7 @@ //! [`ZkVerifierPort`] — public interface for cross-pallet ZK proof verification. //! -//! Other pallets (e.g. `pallet-shielded-pool`, `pallet-account-mapping`) depend -//! only on this trait, never on the concrete pallet internals. +//! Other pallets (e.g. `pallet-shielded-pool`) depend only on this trait, never +//! on the concrete pallet internals. //! //! Each method delegates public-input encoding to [`crate::encoding`] and the //! cryptographic work to [`crate::verifier::verify`]. @@ -50,14 +50,6 @@ pub trait ZkVerifierPort { version: Option, ) -> Result; - /// Verify a private-link dispatch proof. - fn verify_private_link_proof( - proof: &[u8], - commitment: &[u8; 32], - call_hash_fe: &[u8; 32], - version: Option, - ) -> Result; - /// Whether a verification key is registered for `(circuit_id, version)`. fn is_supported_version(circuit_id: u32, version: u32) -> bool; } @@ -126,16 +118,6 @@ impl ZkVerifierPort for Pallet { verifier::verify::(CircuitId::VALUE_PROOF, version, proof, raw).map(|(ok, _)| ok) } - fn verify_private_link_proof( - proof: &[u8], - commitment: &[u8; 32], - call_hash_fe: &[u8; 32], - version: Option, - ) -> Result { - let raw = encoding::encode_private_link(commitment, call_hash_fe); - verifier::verify::(CircuitId::PRIVATE_LINK, version, proof, raw).map(|(ok, _)| ok) - } - fn is_supported_version(circuit_id: u32, version: u32) -> bool { let cid = CircuitId(circuit_id); crate::pallet::VerificationKeys::::contains_key(cid, version) @@ -495,86 +477,6 @@ mod tests { }); } - // ── verify_private_link_proof ────────────────────────────────────────────── - - #[test] - fn private_link_empty_proof_is_rejected() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_private_link_proof( - &[], - &commitment(), - &[0u8; 32], - Some(1), - ), - Error::::EmptyProof - ); - }); - } - - #[test] - fn private_link_no_active_version_returns_circuit_not_found() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_private_link_proof( - &proof(), - &commitment(), - &[0u8; 32], - None, - ), - Error::::CircuitNotFound - ); - }); - } - - #[test] - fn private_link_explicit_unsupported_version_rejected() { - new_test_ext().execute_with(|| { - assert_err!( - as ZkVerifierPort>::verify_private_link_proof( - &proof(), - &commitment(), - &[0u8; 32], - Some(99), - ), - Error::::UnsupportedCircuitVersion - ); - }); - } - - #[test] - fn private_link_happy_path_returns_true() { - new_test_ext().execute_with(|| { - insert_vk(CircuitId::PRIVATE_LINK, 1); - activate(CircuitId::PRIVATE_LINK, 1); - let ok = as ZkVerifierPort>::verify_private_link_proof( - &proof(), - &commitment(), - &[0xFFu8; 32], - None, - ) - .unwrap(); - assert!(ok); - }); - } - - #[test] - fn private_link_explicit_version_overrides_active() { - new_test_ext().execute_with(|| { - insert_vk(CircuitId::PRIVATE_LINK, 1); - insert_vk(CircuitId::PRIVATE_LINK, 2); - activate(CircuitId::PRIVATE_LINK, 1); - let ok = as ZkVerifierPort>::verify_private_link_proof( - &proof(), - &commitment(), - &[0u8; 32], - Some(2), - ) - .unwrap(); - assert!(ok); - }); - } - // ── is_supported_version ─────────────────────────────────────────────────── #[test] diff --git a/frame/zk-verifier/src/types.rs b/frame/zk-verifier/src/types.rs index 9a9a3099..fa93d4e9 100644 --- a/frame/zk-verifier/src/types.rs +++ b/frame/zk-verifier/src/types.rs @@ -29,8 +29,6 @@ impl CircuitId { pub const TRANSFER: Self = Self(1); /// Unshield circuit ID pub const UNSHIELD: Self = Self(2); - /// Private link dispatch circuit ID - pub const PRIVATE_LINK: Self = Self(5); /// Value proof circuit ID — proves commitment encodes (value, asset_id) before fee insertion pub const VALUE_PROOF: Self = Self(6); } diff --git a/frame/zk-verifier/src/verifier.rs b/frame/zk-verifier/src/verifier.rs index f3e73fd7..3bb433aa 100644 --- a/frame/zk-verifier/src/verifier.rs +++ b/frame/zk-verifier/src/verifier.rs @@ -247,7 +247,6 @@ mod tests { for cid in [ CircuitId::TRANSFER, CircuitId::UNSHIELD, - CircuitId::PRIVATE_LINK, CircuitId::VALUE_PROOF, ] { insert_vk(cid, 1); diff --git a/frame/zk-verifier/src/weights.rs b/frame/zk-verifier/src/weights.rs index 676bdd55..895408a7 100644 --- a/frame/zk-verifier/src/weights.rs +++ b/frame/zk-verifier/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for pallet_zk_verifier //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 53.0.0 -//! DATE: 2026-07-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-08-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `ubuntu-32gb-fsn1-1`, CPU: `AMD EPYC-Genoa Processor` +//! HOSTNAME: `ubuntu-32gb-hel1-1`, CPU: `AMD EPYC-Genoa Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -45,6 +45,7 @@ pub trait WeightInfo { fn remove_verification_key() -> Weight; fn retire_version() -> Weight; fn unretire_version() -> Weight; + fn purge_circuit(v: u32, ) -> Weight; fn batch_register_verification_keys(n: u32, ) -> Weight; } @@ -70,12 +71,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[1, 16]`. fn verify_proof(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `445 + n * (32 ±0)` + // Measured: `408 + n * (32 ±0)` // Estimated: `11704 + n * (32 ±0)` - // Minimum execution time: 10_356_729_000 picoseconds. - Weight::from_parts(10_616_358_737, 11704) - // Standard Error: 3_826_694 - .saturating_add(Weight::from_parts(84_861_894, 0).saturating_mul(n.into())) + // Minimum execution time: 11_552_123_000 picoseconds. + Weight::from_parts(11_747_058_966, 11704) + // Standard Error: 895_946 + .saturating_add(Weight::from_parts(35_171_155, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) @@ -96,10 +97,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) fn register_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `65` + // Measured: `139` // Estimated: `22418` - // Minimum execution time: 2_404_770_000 picoseconds. - Weight::from_parts(2_456_662_000, 22418) + // Minimum execution time: 2_624_110_000 picoseconds. + Weight::from_parts(2_661_159_000, 22418) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -117,10 +118,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn set_active_version() -> Weight { // Proof Size summary in bytes: - // Measured: `231` + // Measured: `194` // Estimated: `11704` - // Minimum execution time: 14_791_000 picoseconds. - Weight::from_parts(15_462_000, 11704) + // Minimum execution time: 15_240_000 picoseconds. + Weight::from_parts(16_060_000, 11704) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -138,14 +139,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ZkVerifier::RetiredVersions` (r:0 w:1) /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VkHashes` (r:0 w:1) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VerificationStats` (r:0 w:1) + /// Proof: `ZkVerifier::VerificationStats` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) fn remove_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `205` // Estimated: `11704` - // Minimum execution time: 20_969_000 picoseconds. - Weight::from_parts(21_570_000, 11704) + // Minimum execution time: 25_880_000 picoseconds. + Weight::from_parts(26_700_000, 11704) .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) @@ -163,10 +168,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn retire_version() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `205` // Estimated: `11704` - // Minimum execution time: 20_960_000 picoseconds. - Weight::from_parts(21_369_000, 11704) + // Minimum execution time: 21_730_000 picoseconds. + Weight::from_parts(22_420_000, 11704) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -182,13 +187,46 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn unretire_version() -> Weight { // Proof Size summary in bytes: - // Measured: `196` + // Measured: `159` // Estimated: `3505` - // Minimum execution time: 15_412_000 picoseconds. - Weight::from_parts(15_871_000, 3505) + // Minimum execution time: 16_070_000 picoseconds. + Weight::from_parts(16_710_000, 3505) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Storage: `ZkVerifier::VerificationKeys` (r:65 w:64) + /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VkHashes` (r:65 w:64) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VerificationStats` (r:65 w:64) + /// Proof: `ZkVerifier::VerificationStats` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:65 w:64) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:1) + /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `System::Number` (r:1 w:0) + /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::ExecutionPhase` (r:1 w:0) + /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `System::EventCount` (r:1 w:1) + /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Events` (r:1 w:1) + /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[1, 64]`. + fn purge_circuit(v: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `395 + v * (184 ±0)` + // Estimated: `11704 + v * (10714 ±0)` + // Minimum execution time: 55_810_000 picoseconds. + Weight::from_parts(59_515_646, 11704) + // Standard Error: 26_980 + .saturating_add(Weight::from_parts(10_750_017, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 10714).saturating_mul(v.into())) + } /// Storage: `ZkVerifier::VerificationKeys` (r:20 w:10) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `System::Number` (r:1 w:0) @@ -206,12 +244,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[1, 10]`. fn batch_register_verification_keys(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `65` - // Estimated: `1550 + n * (21428 ±0)` - // Minimum execution time: 2_372_285_000 picoseconds. - Weight::from_parts(2_424_847_000, 1550) - // Standard Error: 9_236_844 - .saturating_add(Weight::from_parts(2_068_662_849, 0).saturating_mul(n.into())) + // Measured: `139` + // Estimated: `1624 + n * (21428 ±0)` + // Minimum execution time: 2_622_549_000 picoseconds. + Weight::from_parts(2_645_430_000, 1624) + // Standard Error: 10_124_288 + .saturating_add(Weight::from_parts(2_254_519_930, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -241,12 +279,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 16]`. fn verify_proof(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `445 + n * (32 ±0)` + // Measured: `408 + n * (32 ±0)` // Estimated: `11704 + n * (32 ±0)` - // Minimum execution time: 10_356_729_000 picoseconds. - Weight::from_parts(10_616_358_737, 11704) - // Standard Error: 3_826_694 - .saturating_add(Weight::from_parts(84_861_894, 0).saturating_mul(n.into())) + // Minimum execution time: 11_552_123_000 picoseconds. + Weight::from_parts(11_747_058_966, 11704) + // Standard Error: 895_946 + .saturating_add(Weight::from_parts(35_171_155, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(Weight::from_parts(0, 32).saturating_mul(n.into())) @@ -267,10 +305,10 @@ impl WeightInfo for () { /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) fn register_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `65` + // Measured: `139` // Estimated: `22418` - // Minimum execution time: 2_404_770_000 picoseconds. - Weight::from_parts(2_456_662_000, 22418) + // Minimum execution time: 2_624_110_000 picoseconds. + Weight::from_parts(2_661_159_000, 22418) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -288,10 +326,10 @@ impl WeightInfo for () { /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn set_active_version() -> Weight { // Proof Size summary in bytes: - // Measured: `231` + // Measured: `194` // Estimated: `11704` - // Minimum execution time: 14_791_000 picoseconds. - Weight::from_parts(15_462_000, 11704) + // Minimum execution time: 15_240_000 picoseconds. + Weight::from_parts(16_060_000, 11704) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -309,14 +347,18 @@ impl WeightInfo for () { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ZkVerifier::RetiredVersions` (r:0 w:1) /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VkHashes` (r:0 w:1) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VerificationStats` (r:0 w:1) + /// Proof: `ZkVerifier::VerificationStats` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) fn remove_verification_key() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `205` // Estimated: `11704` - // Minimum execution time: 20_969_000 picoseconds. - Weight::from_parts(21_570_000, 11704) + // Minimum execution time: 25_880_000 picoseconds. + Weight::from_parts(26_700_000, 11704) .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `ZkVerifier::VerificationKeys` (r:1 w:0) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) @@ -334,10 +376,10 @@ impl WeightInfo for () { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn retire_version() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `205` // Estimated: `11704` - // Minimum execution time: 20_960_000 picoseconds. - Weight::from_parts(21_369_000, 11704) + // Minimum execution time: 21_730_000 picoseconds. + Weight::from_parts(22_420_000, 11704) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -353,13 +395,46 @@ impl WeightInfo for () { /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn unretire_version() -> Weight { // Proof Size summary in bytes: - // Measured: `196` + // Measured: `159` // Estimated: `3505` - // Minimum execution time: 15_412_000 picoseconds. - Weight::from_parts(15_871_000, 3505) + // Minimum execution time: 16_070_000 picoseconds. + Weight::from_parts(16_710_000, 3505) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Storage: `ZkVerifier::VerificationKeys` (r:65 w:64) + /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VkHashes` (r:65 w:64) + /// Proof: `ZkVerifier::VkHashes` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::VerificationStats` (r:65 w:64) + /// Proof: `ZkVerifier::VerificationStats` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::RetiredVersions` (r:65 w:64) + /// Proof: `ZkVerifier::RetiredVersions` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `ZkVerifier::ActiveCircuitVersion` (r:1 w:1) + /// Proof: `ZkVerifier::ActiveCircuitVersion` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) + /// Storage: `System::Number` (r:1 w:0) + /// Proof: `System::Number` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::ExecutionPhase` (r:1 w:0) + /// Proof: `System::ExecutionPhase` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `System::EventCount` (r:1 w:1) + /// Proof: `System::EventCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Events` (r:1 w:1) + /// Proof: `System::Events` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[1, 64]`. + fn purge_circuit(v: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `395 + v * (184 ±0)` + // Estimated: `11704 + v * (10714 ±0)` + // Minimum execution time: 55_810_000 picoseconds. + Weight::from_parts(59_515_646, 11704) + // Standard Error: 26_980 + .saturating_add(Weight::from_parts(10_750_017, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((4_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 10714).saturating_mul(v.into())) + } /// Storage: `ZkVerifier::VerificationKeys` (r:20 w:10) /// Proof: `ZkVerifier::VerificationKeys` (`max_values`: None, `max_size`: Some(8239), added: 10714, mode: `MaxEncodedLen`) /// Storage: `System::Number` (r:1 w:0) @@ -377,12 +452,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 10]`. fn batch_register_verification_keys(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `65` - // Estimated: `1550 + n * (21428 ±0)` - // Minimum execution time: 2_372_285_000 picoseconds. - Weight::from_parts(2_424_847_000, 1550) - // Standard Error: 9_236_844 - .saturating_add(Weight::from_parts(2_068_662_849, 0).saturating_mul(n.into())) + // Measured: `139` + // Estimated: `1624 + n * (21428 ±0)` + // Minimum execution time: 2_622_549_000 picoseconds. + Weight::from_parts(2_645_430_000, 1624) + // Standard Error: 10_124_288 + .saturating_add(Weight::from_parts(2_254_519_930, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) diff --git a/primitives/zk-verifier/README.md b/primitives/zk-verifier/README.md index 4a40f29c..39803fcb 100644 --- a/primitives/zk-verifier/README.md +++ b/primitives/zk-verifier/README.md @@ -72,7 +72,6 @@ let inputs = parse_public_inputs_from_snarkjs(&["12345", "67890"])?; | 1 | `transfer` | 7 | | 2 | `unshield` | 6 | | 6 | `value_proof` | 4 | -| 5 | `private_link` | 2 | ## Weight estimation diff --git a/primitives/zk-verifier/src/lib.rs b/primitives/zk-verifier/src/lib.rs index bd04618b..68c3aeea 100644 --- a/primitives/zk-verifier/src/lib.rs +++ b/primitives/zk-verifier/src/lib.rs @@ -30,8 +30,8 @@ pub use snarkjs::SnarkjsProofPoints; pub use snarkjs::{parse_proof_from_snarkjs, parse_public_inputs_from_snarkjs}; pub use types::{ expected_public_inputs, Proof, PublicInputs, VerifierError, VerifyingKey, - BASE_VERIFICATION_COST, CIRCUIT_ID_PRIVATE_LINK, CIRCUIT_ID_TRANSFER, CIRCUIT_ID_UNSHIELD, - CIRCUIT_ID_VALUE_PROOF, MAX_PUBLIC_INPUTS, PER_INPUT_COST, PRIVATE_LINK_PUBLIC_INPUTS, - TRANSFER_PUBLIC_INPUTS, UNSHIELD_PUBLIC_INPUTS, VALUE_PROOF_PUBLIC_INPUTS, + BASE_VERIFICATION_COST, CIRCUIT_ID_TRANSFER, CIRCUIT_ID_UNSHIELD, CIRCUIT_ID_VALUE_PROOF, + MAX_PUBLIC_INPUTS, PER_INPUT_COST, TRANSFER_PUBLIC_INPUTS, UNSHIELD_PUBLIC_INPUTS, + VALUE_PROOF_PUBLIC_INPUTS, }; pub use verifier::Groth16Verifier; diff --git a/primitives/zk-verifier/src/types.rs b/primitives/zk-verifier/src/types.rs index fab3750a..7fccab93 100644 --- a/primitives/zk-verifier/src/types.rs +++ b/primitives/zk-verifier/src/types.rs @@ -20,8 +20,6 @@ pub const CIRCUIT_ID_TRANSFER: u8 = 1; pub const CIRCUIT_ID_UNSHIELD: u8 = 2; /// Circuit identifier for value proof operations (proves note commitment encodes declared value). pub const CIRCUIT_ID_VALUE_PROOF: u8 = 6; -/// Circuit identifier for private link dispatch operations. -pub const CIRCUIT_ID_PRIVATE_LINK: u8 = 5; /// Number of public inputs for the transfer circuit. /// Public inputs: [merkle_root, nullifier1, nullifier2, commitment1, commitment2, asset_id, fee] @@ -32,9 +30,6 @@ pub const UNSHIELD_PUBLIC_INPUTS: usize = 7; /// Number of public signals for the value proof circuit. /// Signals: [commitment(32B), value(8B), asset_id(4B), owner_hash(32B)] = 4 field elements. pub const VALUE_PROOF_PUBLIC_INPUTS: usize = 4; -/// Number of public inputs for the private link circuit. -/// Public inputs: [commitment(32B LE field element), call_hash_fe(32B LE field element)] -pub const PRIVATE_LINK_PUBLIC_INPUTS: usize = 2; /// Base cost for Groth16 verification (pairing operations). pub const BASE_VERIFICATION_COST: u64 = 100_000; @@ -49,7 +44,6 @@ pub const fn expected_public_inputs(circuit_id: u8) -> Option { match circuit_id { CIRCUIT_ID_TRANSFER => Some(TRANSFER_PUBLIC_INPUTS), CIRCUIT_ID_UNSHIELD => Some(UNSHIELD_PUBLIC_INPUTS), - CIRCUIT_ID_PRIVATE_LINK => Some(PRIVATE_LINK_PUBLIC_INPUTS), CIRCUIT_ID_VALUE_PROOF => Some(VALUE_PROOF_PUBLIC_INPUTS), _ => None, } @@ -305,7 +299,6 @@ mod tests { assert_eq!(CIRCUIT_ID_TRANSFER, 1); assert_eq!(CIRCUIT_ID_UNSHIELD, 2); assert_eq!(CIRCUIT_ID_VALUE_PROOF, 6); - assert_eq!(CIRCUIT_ID_PRIVATE_LINK, 5); } #[test] @@ -313,17 +306,18 @@ mod tests { assert_eq!(TRANSFER_PUBLIC_INPUTS, 7); assert_eq!(UNSHIELD_PUBLIC_INPUTS, 7); assert_eq!(VALUE_PROOF_PUBLIC_INPUTS, 4); - assert_eq!(PRIVATE_LINK_PUBLIC_INPUTS, 2); } #[test] fn expected_public_inputs_maps_known_circuits() { assert_eq!(expected_public_inputs(CIRCUIT_ID_TRANSFER), Some(7)); assert_eq!(expected_public_inputs(CIRCUIT_ID_UNSHIELD), Some(7)); - assert_eq!(expected_public_inputs(CIRCUIT_ID_PRIVATE_LINK), Some(2)); assert_eq!(expected_public_inputs(CIRCUIT_ID_VALUE_PROOF), Some(4)); // Unknown / unmapped circuit ids (e.g. shield=3) return None. assert_eq!(expected_public_inputs(3), None); + // Circuit 5 (retired private-link) is no longer a known circuit: a VK + // registered under that id would skip arity validation entirely. + assert_eq!(expected_public_inputs(5), None); assert_eq!(expected_public_inputs(0), None); assert_eq!(expected_public_inputs(99), None); } diff --git a/scripts/generate-specs/testnet.sh b/scripts/generate-specs/testnet.sh index 3782afa5..68e72ee1 100755 --- a/scripts/generate-specs/testnet.sh +++ b/scripts/generate-specs/testnet.sh @@ -42,8 +42,8 @@ cat "$DEPLOY_DIR/testnet-spec-plain.json" | jq ' .properties.tokenSymbol = "ORB" | .properties.tokenDecimals = 18 | .bootNodes = [ - "/dns/rpc-1.testnet.orbinum.io/tcp/30333/p2p/12D3KooWBzqb1AFLQJd4NooU7q6dSsYBFL85A8RPsJDLesfxHvbW", - "/dns/rpc-2.testnet.orbinum.io/tcp/30333/p2p/12D3KooWKiLEh2Z8XZYGejL5ZMR6rwEdZHPTpVDW3nsXfSW8paLt" + "/dns/p2p-1.testnet.orbinum.io/tcp/30333/p2p/12D3KooWBzqb1AFLQJd4NooU7q6dSsYBFL85A8RPsJDLesfxHvbW", + "/dns/p2p-2.testnet.orbinum.io/tcp/30333/p2p/12D3KooWKiLEh2Z8XZYGejL5ZMR6rwEdZHPTpVDW3nsXfSW8paLt" ] | .telemetryEndpoints = [ ["/dns4/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", 0] diff --git a/scripts/run_benchmarks.sh b/scripts/run_benchmarks.sh index 4b94f6e7..ff043f87 100755 --- a/scripts/run_benchmarks.sh +++ b/scripts/run_benchmarks.sh @@ -50,7 +50,7 @@ if [[ ! -f "$NODE" ]]; then fi mkdir -p "$SCRATCH_DIR" -mkdir -p ./frame/account-mapping/src ./frame/zk-verifier/src ./frame/shielded-pool/src ./frame/relayer/src +mkdir -p ./frame/zk-verifier/src ./frame/shielded-pool/src ./frame/relayer/src run_bench() { local pallet="$1" @@ -79,7 +79,6 @@ echo "" echo "[2/3] Running benchmarks for full runtime set..." # Pallets con archivo de weights propio en el repo -run_bench "pallet_account_mapping" "./frame/account-mapping/src/weights.rs" run_bench "pallet_zk_verifier" "./frame/zk-verifier/src/weights.rs" run_bench "pallet_shielded_pool" "./frame/shielded-pool/src/weights.rs" run_bench "pallet_relayer" "./frame/relayer/src/weights.rs" diff --git a/scripts/vk/README.md b/scripts/vk/README.md index 13b3f19a..4aa6bc4a 100644 --- a/scripts/vk/README.md +++ b/scripts/vk/README.md @@ -10,7 +10,7 @@ This folder centralizes all Verification Key (VK) operations for development and - `set-active` - `remove` - `workflows/setup-dev.sh` - - DEV bootstrap for fixed circuits `1,2,6,5`. + - DEV bootstrap for fixed circuits `1,2,6`. - `workflows/rotate-dev.sh` - Version rotation `old -> new` with RPC validation and optional `--remove-old`. - `policy/verify-window-dev.sh` @@ -33,6 +33,29 @@ This folder centralizes all Verification Key (VK) operations for development and - `bash scripts/vk/workflows/rotate-dev.sh 2 ws://127.0.0.1:9944 "//Alice" 1 --remove-old` - Verify window: - `bash scripts/vk/policy/verify-window-dev.sh 2 http://127.0.0.1:9944 "1,2" true` +- Purge a retired circuit: + - `bash scripts/vk/lib/registry.sh purge 5 ws://127.0.0.1:9944 "//Alice"` + +## Retiring a whole circuit + +`remove` and `retire` both refuse to touch a circuit's active version — the guard +that stops a live circuit from ending up with no key to verify against. A circuit +with a single registered version is therefore unreachable by either call, so +retiring one as a whole needs `purge`. + +`purge_circuit` clears all five maps (`VerificationKeys`, `VkHashes`, +`VerificationStats`, `RetiredVersions`, `ActiveCircuitVersion`) and accepts a +circuit **only** when the runtime no longer implements it, i.e. when +`expected_public_inputs` returns `None`. Transfer (1), unshield (2) and +value_proof (6) are rejected with `CircuitStillInUse` for as long as they remain +compiled in — storage contents cannot override that. + +Order matters: deploy the runtime that drops the circuit **first**, then purge. +Purging against a runtime that still knows the id just fails. + +For the `private_link` circuit (id 5) no manual call is needed — +`pallet_zk_verifier::migrations::v1::MigrateToV1` clears it during the runtime +upgrade. ## Security diff --git a/scripts/vk/lib/registry.sh b/scripts/vk/lib/registry.sh index 0b6e5860..369f2576 100755 --- a/scripts/vk/lib/registry.sh +++ b/scripts/vk/lib/registry.sh @@ -10,10 +10,11 @@ set -euo pipefail # bash scripts/vk/lib/registry.sh register # bash scripts/vk/lib/registry.sh set-active # bash scripts/vk/lib/registry.sh remove +# bash scripts/vk/lib/registry.sh purge # -# # Register and activate all 4 circuits in ONE atomic transaction: +# # Register and activate all 3 circuits in ONE atomic transaction: # bash scripts/vk/lib/registry.sh batch-register \ -# \ +# \ # # # EXAMPLES: @@ -24,7 +25,6 @@ set -euo pipefail # ./artifacts/verification_key_transfer.json \ # ./artifacts/verification_key_unshield.json \ # ./artifacts/verification_key_value_proof.json \ -# ./artifacts/verification_key_private_link.json \ # ws://127.0.0.1:9944 "//Alice" # ============================================================================= @@ -41,9 +41,10 @@ log() { echo "[$(date '+%H:%M:%S')] $*" } -[[ -n "$ACTION" ]] || err "Missing action: register | set-active | remove | batch-register" +[[ -n "$ACTION" ]] || err "Missing action: register | set-active | remove | purge | batch-register" [[ -n "$CIRCUIT_ID" ]] || err "Missing circuit_id (or for batch-register)" -[[ -n "$VERSION" ]] || err "Missing version" +# `purge` targets a whole circuit, so it takes no : its $3 is the RPC URL. +[[ "$ACTION" == "purge" || -n "$VERSION" ]] || err "Missing version" # Initialize all optional variables to avoid unbound variable errors with set -u VK_FILE="" @@ -52,7 +53,6 @@ BATCH_SET_ACTIVE="" BATCH_VK_TRANSFER="" BATCH_VK_UNSHIELD="" BATCH_VK_VALUE_PROOF="" -BATCH_VK_PRIVATE_LINK="" RPC_WS="" SUDO_SEED="" @@ -68,26 +68,32 @@ case "$ACTION" in SUDO_SEED="${5:-}" VK_FILE="" ;; + purge) + # purge takes no : $2=circuit_id $3=rpc $4=seed + RPC_WS="${3:-}" + SUDO_SEED="${4:-}" + VK_FILE="" + VERSION="0" # unused by the call; keeps the log line below well-formed + ;; batch-register) - # For batch-register the positional args shift: $2=version $3=set_active $4..7=vk_files $8=rpc $9=seed + # For batch-register the positional args shift: $2=version $3=set_active $4..6=vk_files $7=rpc $8=seed # Re-read with cleaner names to avoid confusion with CIRCUIT_ID/VERSION used by other actions BATCH_VERSION="${2:-}" BATCH_SET_ACTIVE="${3:-1}" BATCH_VK_TRANSFER="${4:-}" BATCH_VK_UNSHIELD="${5:-}" BATCH_VK_VALUE_PROOF="${6:-}" - BATCH_VK_PRIVATE_LINK="${7:-}" - RPC_WS="${8:-}" - SUDO_SEED="${9:-}" + RPC_WS="${7:-}" + SUDO_SEED="${8:-}" [[ -n "$BATCH_VERSION" ]] || err "batch-register: missing " [[ "$BATCH_VERSION" =~ ^[0-9]+$ ]] || err "batch-register: version must be an integer >= 0" [[ "$BATCH_SET_ACTIVE" =~ ^[01]$ ]] || err "batch-register: set_active must be 0 or 1" - for _f in "$BATCH_VK_TRANSFER" "$BATCH_VK_UNSHIELD" "$BATCH_VK_VALUE_PROOF" "$BATCH_VK_PRIVATE_LINK"; do + for _f in "$BATCH_VK_TRANSFER" "$BATCH_VK_UNSHIELD" "$BATCH_VK_VALUE_PROOF"; do [[ -f "$_f" ]] || err "VK file not found: $_f" done ;; *) - err "Invalid action: $ACTION (use register | set-active | remove | batch-register)" + err "Invalid action: $ACTION (use register | set-active | remove | purge | batch-register)" ;; esac @@ -113,7 +119,7 @@ log "RPC: $RPC_WS" node - "$ACTION" "$CIRCUIT_ID" "$VERSION" "$VK_FILE" "$RPC_WS" "$SUDO_SEED" \ "$BATCH_VERSION" "$BATCH_SET_ACTIVE" \ - "$BATCH_VK_TRANSFER" "$BATCH_VK_UNSHIELD" "$BATCH_VK_VALUE_PROOF" "$BATCH_VK_PRIVATE_LINK" << 'JS' + "$BATCH_VK_TRANSFER" "$BATCH_VK_UNSHIELD" "$BATCH_VK_VALUE_PROOF" << 'JS' const fs = require('fs'); const path = require('path'); const { ApiPromise, WsProvider } = require('@polkadot/api'); @@ -121,7 +127,7 @@ const { Keyring } = require('@polkadot/keyring'); const [,, action, circuitIdRaw, versionRaw, vkFile, rpcWs, sudoSeed, batchVersion, batchSetActive, - batchVkTransfer, batchVkUnshield, batchVkValueProof, batchVkPrivateLink] = process.argv; + batchVkTransfer, batchVkUnshield, batchVkValueProof] = process.argv; function assertNumber(name, value) { if (!Number.isInteger(value) || value < 0) { @@ -160,8 +166,12 @@ function assertNumber(name, value) { innerCall = api.tx.zkVerifier.setActiveVersion(circuitId, version); } else if (action === 'remove') { innerCall = api.tx.zkVerifier.removeVerificationKey(circuitId, version); + } else if (action === 'purge') { + // Wipes every version of a circuit the runtime no longer implements. + // The pallet rejects ids it still knows (transfer/unshield/value_proof). + innerCall = api.tx.zkVerifier.purgeCircuit(circuitId); } else if (action === 'batch-register') { - // circuit IDs: transfer=1, unshield=2, value_proof=6, private_link=5 + // circuit IDs: transfer=1, unshield=2, value_proof=6 const ver = Number(batchVersion); const setActive = batchSetActive === '1'; assertNumber('batch_version', ver); @@ -170,7 +180,6 @@ function assertNumber(name, value) { { circuitId: 1, file: batchVkTransfer }, { circuitId: 2, file: batchVkUnshield }, { circuitId: 6, file: batchVkValueProof }, - { circuitId: 5, file: batchVkPrivateLink }, ]; const entries = vkFiles.map(({ circuitId: cid, file }) => { diff --git a/scripts/vk/policy/verify-window-dev.sh b/scripts/vk/policy/verify-window-dev.sh index f35871c8..3d075de9 100755 --- a/scripts/vk/policy/verify-window-dev.sh +++ b/scripts/vk/policy/verify-window-dev.sh @@ -5,7 +5,7 @@ set -euo pipefail # scripts/vk/policy/verify-window-dev.sh # Verifies VK version-window policy for fixed DEV circuits. # -# Verified circuits: 1 (transfer), 2 (unshield), 6 (value_proof), 5 (private_link) +# Verified circuits: 1 (transfer), 2 (unshield), 6 (value_proof) # # USAGE: # bash scripts/vk/policy/verify-window-dev.sh [rpc_http] [required_versions_csv] [strict] @@ -41,7 +41,7 @@ log() { [[ "$EXPECTED_ACTIVE" =~ ^[0-9]+$ ]] || err "expected_active must be an integer >= 0" [[ "$STRICT" == "true" || "$STRICT" == "false" ]] || err "strict must be true|false" -CIRCUITS=(1 2 6 5) +CIRCUITS=(1 2 6) log "Verifying VK window" log "RPC: $RPC_HTTP" @@ -97,4 +97,4 @@ PY done -log "✅ VK window is valid for all 4 circuits" +log "✅ VK window is valid for all 3 circuits" diff --git a/scripts/vk/workflows/rotate-dev.sh b/scripts/vk/workflows/rotate-dev.sh index 991a57a0..215bf485 100755 --- a/scripts/vk/workflows/rotate-dev.sh +++ b/scripts/vk/workflows/rotate-dev.sh @@ -3,13 +3,13 @@ set -euo pipefail # ============================================================================ # scripts/vk/workflows/rotate-dev.sh -# DEV on-chain VK rotation for the 4 supported circuits. +# DEV on-chain VK rotation for the 3 supported circuits. # # Flow: -# 1) register NEW_VERSION on 1,2,6,5 -# 2) set-active NEW_VERSION on 1,2,6,5 +# 1) register NEW_VERSION on 1,2,6 +# 2) set-active NEW_VERSION on 1,2,6 # 3) RPC validation per circuit -# 4) optional: remove OLD_VERSION on 1,2,6,5 (if --remove-old) +# 4) optional: remove OLD_VERSION on 1,2,6 (if --remove-old) # # USAGE: # bash scripts/vk/workflows/rotate-dev.sh [rpc_ws] [sudo_seed] [old_version] @@ -80,7 +80,6 @@ convert_vk() { VK_TRANSFER_BIN=$(convert_vk "$ARTIFACTS_DIR/verification_key_transfer.json") VK_UNSHIELD_BIN=$(convert_vk "$ARTIFACTS_DIR/verification_key_unshield.json") VK_VALUE_PROOF_BIN=$(convert_vk "$ARTIFACTS_DIR/verification_key_value_proof.json") -VK_PRIVATE_LINK_BIN=$(convert_vk "$ARTIFACTS_DIR/verification_key_private_link.json") validate_rpc() { local circuit_id="$1" @@ -112,17 +111,16 @@ log "old_version: $OLD_VERSION" log "new_version: $NEW_VERSION" log "remove_old: $REMOVE_OLD" -# Register and activate all 4 circuits in ONE atomic transaction -log "Batch registering + activating all 4 circuits (v$NEW_VERSION) in a single tx..." +# Register and activate all 3 circuits in ONE atomic transaction +log "Batch registering + activating all 3 circuits (v$NEW_VERSION) in a single tx..." bash "$VK_REGISTRY_SCRIPT" batch-register \ "$NEW_VERSION" 1 \ - "$VK_TRANSFER_BIN" "$VK_UNSHIELD_BIN" "$VK_VALUE_PROOF_BIN" "$VK_PRIVATE_LINK_BIN" \ + "$VK_TRANSFER_BIN" "$VK_UNSHIELD_BIN" "$VK_VALUE_PROOF_BIN" \ "$RPC_WS" "$SUDO_SEED" validate_rpc 1 "transfer" validate_rpc 2 "unshield" validate_rpc 6 "value_proof" -validate_rpc 5 "private_link" if [[ "$REMOVE_OLD" == true ]]; then log "Checking pre-remove window (required=[${OLD_VERSION},${NEW_VERSION}])" @@ -131,15 +129,14 @@ if [[ "$REMOVE_OLD" == true ]]; then remove_old 1 "transfer" remove_old 2 "unshield" remove_old 6 "value_proof" - remove_old 5 "private_link" log "Checking post-remove state (required=[${NEW_VERSION}])" bash "$VERIFY_WINDOW_SCRIPT" "$NEW_VERSION" "$RPC_HTTP" "${NEW_VERSION}" false - log "old_version v$OLD_VERSION removed from all 4 circuits" + log "old_version v$OLD_VERSION removed from all 3 circuits" fi # Remove generated .bin files — they are build artefacts excluded from git -rm -f "$VK_TRANSFER_BIN" "$VK_UNSHIELD_BIN" "$VK_VALUE_PROOF_BIN" "$VK_PRIVATE_LINK_BIN" +rm -f "$VK_TRANSFER_BIN" "$VK_UNSHIELD_BIN" "$VK_VALUE_PROOF_BIN" log "✅ VK rotation completed" diff --git a/scripts/vk/workflows/setup-dev.sh b/scripts/vk/workflows/setup-dev.sh index c2ddcd82..21263120 100755 --- a/scripts/vk/workflows/setup-dev.sh +++ b/scripts/vk/workflows/setup-dev.sh @@ -5,11 +5,10 @@ set -euo pipefail # scripts/vk/workflows/setup-dev.sh # DEV setup for on-chain VKs (main entrypoint for developers) # -# Registers + activates ONLY these 4 circuits: +# Registers + activates ONLY these 3 circuits: # 1 transfer # 2 unshield # 6 value_proof -# 5 private_link # # VK artifacts are resolved from the @orbinum/circuits npm package via unpkg: # 1. Fetches manifest.json to determine the latest package_version @@ -123,16 +122,15 @@ log "RPC: $RPC_WS | Circuit version: $VERSION" VK_TRANSFER=$(resolve_vk "transfer") VK_UNSHIELD=$(resolve_vk "unshield") VK_VALUE_PROOF=$(resolve_vk "value_proof") -VK_PRIVATE_LINK=$(resolve_vk "private_link") -log "Circuits: transfer(1), unshield(2), value_proof(6), private_link(5)" +log "Circuits: transfer(1), unshield(2), value_proof(6)" # ─── Register + activate ───────────────────────────────────────────────────── -log "Batch registering + activating all 4 circuits (v$VERSION) in a single tx..." +log "Batch registering + activating all 3 circuits (v$VERSION) in a single tx..." bash "$VK_REGISTRY_SCRIPT" batch-register \ "$VERSION" 1 \ - "$VK_TRANSFER" "$VK_UNSHIELD" "$VK_VALUE_PROOF" "$VK_PRIVATE_LINK" \ + "$VK_TRANSFER" "$VK_UNSHIELD" "$VK_VALUE_PROOF" \ "$RPC_WS" "$SUDO_SEED" # ─── Cleanup ───────────────────────────────────────────────────────────────── @@ -144,7 +142,7 @@ fi # Remove any .bin files produced from local artifacts (they are generated # artefacts and are excluded from version control via .gitignore) -for circuit in transfer unshield value_proof private_link; do +for circuit in transfer unshield value_proof; do rm -f "$ARTIFACTS_DIR/verification_key_${circuit}.bin" done diff --git a/template/node/Cargo.toml b/template/node/Cargo.toml index aa1d3a8e..32b49273 100644 --- a/template/node/Cargo.toml +++ b/template/node/Cargo.toml @@ -56,8 +56,6 @@ sp-timestamp = { workspace = true, features = ["default"] } sp-transaction-pool = { workspace = true, features = ["default"] } # These dependencies are used for RPC frame-system-rpc-runtime-api = { workspace = true } -pallet-account-mapping-rpc = { workspace = true } -pallet-account-mapping-runtime-api = { workspace = true } pallet-relayer-rpc = { workspace = true } pallet-relayer-runtime-api = { workspace = true } pallet-shielded-pool-rpc = { workspace = true } diff --git a/template/node/src/rpc/mod.rs b/template/node/src/rpc/mod.rs index 28f48da3..9e184020 100644 --- a/template/node/src/rpc/mod.rs +++ b/template/node/src/rpc/mod.rs @@ -74,7 +74,6 @@ where C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: fp_rpc::ConvertTransactionRuntimeApi, C::Api: fp_rpc::EthereumRuntimeRPCApi, - C::Api: pallet_account_mapping_runtime_api::AccountMappingRuntimeApi, C::Api: pallet_shielded_pool_runtime_api::ShieldedPoolRuntimeApi, C::Api: pallet_zk_verifier_runtime_api::ZkVerifierRuntimeApi, C::Api: pallet_relayer_runtime_api::RelayerRuntimeApi, @@ -86,7 +85,6 @@ where CIDP: CreateInherentDataProviders + Send + 'static, CT: fp_rpc::ConvertTransaction<::Extrinsic> + Send + Sync + 'static, { - use pallet_account_mapping_rpc::{AccountMapping, AccountMappingApiServer}; use pallet_relayer_rpc::{Relayer, RelayerApiServer}; use pallet_shielded_pool_rpc::{ShieldedPool, ShieldedPoolApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; @@ -108,7 +106,6 @@ where io.merge(System::new(client.clone(), pool.clone()).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; - io.merge(AccountMapping::new(client.clone()).into_rpc())?; io.merge(ShieldedPool::new(client.clone()).into_rpc())?; io.merge(ZkVerifier::new(client.clone()).into_rpc())?; io.merge(Relayer::new(client.clone()).into_rpc())?; diff --git a/template/node/src/service.rs b/template/node/src/service.rs index 5668ec59..9e254098 100644 --- a/template/node/src/service.rs +++ b/template/node/src/service.rs @@ -337,8 +337,6 @@ where RA: ConstructRuntimeApi>, RA: Send + Sync + 'static, RA::RuntimeApi: RuntimeApiCollection, - RA::RuntimeApi: - pallet_account_mapping_runtime_api::AccountMappingRuntimeApi, RA::RuntimeApi: pallet_zk_verifier_runtime_api::ZkVerifierRuntimeApi, RA::RuntimeApi: pallet_relayer_runtime_api::RelayerRuntimeApi, HF: HostFunctionsT + 'static, diff --git a/template/runtime/Cargo.toml b/template/runtime/Cargo.toml index 7a41fe41..8073e16d 100644 --- a/template/runtime/Cargo.toml +++ b/template/runtime/Cargo.toml @@ -45,8 +45,6 @@ frame-system = { workspace = true } frame-system-benchmarking = { workspace = true, optional = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { workspace = true, optional = true } -pallet-account-mapping = { workspace = true } -pallet-account-mapping-runtime-api = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } @@ -68,7 +66,6 @@ pallet-dynamic-fee = { workspace = true } pallet-ethereum = { workspace = true } pallet-evm = { workspace = true } pallet-evm-chain-id = { workspace = true } -pallet-evm-precompile-account-mapping = { workspace = true } pallet-evm-precompile-balances = { workspace = true } pallet-evm-precompile-curve25519 = { workspace = true } pallet-evm-precompile-curve25519-benchmarking = { workspace = true } @@ -135,8 +132,6 @@ std = [ "frame-system-benchmarking?/std", "pallet-aura/std", "pallet-authorship/std", - "pallet-account-mapping/std", - "pallet-account-mapping-runtime-api/std", "pallet-balances/std", "pallet-grandpa/std", "pallet-session/std", @@ -161,7 +156,6 @@ std = [ "pallet-evm-precompile-simple/std", "pallet-evm-precompile-curve25519/std", "pallet-evm-precompile-curve25519-benchmarking/std", - "pallet-evm-precompile-account-mapping/std", "pallet-evm-precompile-balances/std", "pallet-evm-precompile-shielded-pool/std", # Orbinum Privacy @@ -181,7 +175,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-account-mapping/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", diff --git a/template/runtime/RUNTIME_VERSIONS.md b/template/runtime/RUNTIME_VERSIONS.md index 3a3a86da..14d2298e 100644 --- a/template/runtime/RUNTIME_VERSIONS.md +++ b/template/runtime/RUNTIME_VERSIONS.md @@ -22,6 +22,7 @@ The genesis reset (`69d1b837`) set `spec_version` back to 1 and | spec | tx | Date | Commit | Change | |------|----|------|--------|--------| +| 6 | 2 | 2026-08-03 | — | `pallet-account-mapping` and its precompile (index 14, address 0x0800) removed, along with the `private_link` circuit (id 5) and its verification key. Index 14 is retired and must not be reassigned. zk-verifier 0.10.0 gains `purge_circuit` (call index 7, Root) plus `STORAGE_VERSION` 1 and `MigrateToV1`, which drops the stranded circuit-5 key that no extrinsic could reach. `transaction_version` moves because the new call index changes extrinsic encoding. | | 5 | 1 | 2026-07-30 | — | shielded-pool 0.12.0: multi-tree forest. Full trees seal (`TreeSealed`, permanent `SealedTreeRoots` anchors) and inserts roll over to a fresh tree — the 2^20-note network ceiling is gone. New Config `MaxLeavesPerTree` (2^20), `STORAGE_VERSION` 2 (`MigrateToV2`, version-only), runtime API v2 (`get_forest_info`, `get_root_for_leaf`). No circuit/extrinsic/ABI changes. | | 4 | 1 | 2026-07-30 | `63caafca` | shielded-pool 0.11.0: `MerkleNodes` storage (internal nodes written on every insert) + `MigrateToV1` migration (backfill, `STORAGE_VERSION` 1). O(depth) Merkle proofs. Weights re-benchmarked. The upgrade block runs the one-shot migration (~3s at ~90k leaves). | | 3 | 1 | 2026-07-27 | `d9d40244` | Build with the metadata hash `CheckMetadataHash` requires. | diff --git a/template/runtime/src/account_mapping_runtime.rs b/template/runtime/src/evm_account.rs similarity index 62% rename from template/runtime/src/account_mapping_runtime.rs rename to template/runtime/src/evm_account.rs index 3917f4a9..614979a0 100644 --- a/template/runtime/src/account_mapping_runtime.rs +++ b/template/runtime/src/evm_account.rs @@ -1,9 +1,8 @@ use core::marker::PhantomData; -use frame_support::sp_runtime::traits::Convert; use sp_core::H160; -use crate::{AccountId, Runtime}; +use crate::AccountId; pub const EVM_ACCOUNT_MARKER: [u8; 12] = [0x00u8; 12]; @@ -22,33 +21,20 @@ pub fn evm_h160_to_account_id(address: H160) -> AccountId { AccountId::from(evm_h160_to_account_id_bytes(address)) } -pub fn try_evm_h160_from_account_id(account_id: &AccountId) -> Option { - let bytes: &[u8; 32] = account_id.as_ref(); - if bytes[20..] == EVM_ACCOUNT_MARKER { - Some(H160::from_slice(&bytes[0..20])) - } else { - None - } -} - -pub struct AccountIdToEvmAddress; -impl Convert> for AccountIdToEvmAddress { - fn convert(account_id: AccountId) -> Option { - try_evm_h160_from_account_id(&account_id) - } -} - +/// Maps an EVM `H160` to its `AccountId32` as `[address | 0x00×12]`. +/// +/// The mapping is purely structural: the same 20 bytes always produce the same +/// `AccountId32`, with no storage read. Secp256k1 accounts signing through +/// `OrbinumSignature` land on exactly this account, which is what makes the EVM +/// and Substrate views of a key one and the same account. pub struct EeSuffixAddressMapping(pub PhantomData); impl pallet_evm::AddressMapping for EeSuffixAddressMapping where - T: pallet_evm::Config + pallet_account_mapping::Config, + T: pallet_evm::Config, T::AccountId: From<[u8; 32]>, { fn into_account_id(address: H160) -> T::AccountId { - if let Some(mapped) = pallet_account_mapping::Pallet::::mapped_account(address) { - return mapped; - } T::AccountId::from(evm_h160_to_account_id_bytes(address)) } } @@ -63,9 +49,7 @@ where type Success = AccountId; fn try_address_origin(address: &H160, origin: OuterOrigin) -> Result { - let expected_account: AccountId = - pallet_account_mapping::Pallet::::mapped_account(*address) - .unwrap_or_else(|| evm_h160_to_account_id(*address)); + let expected_account: AccountId = evm_h160_to_account_id(*address); origin.into().and_then(|o| match o { frame_system::RawOrigin::Signed(who) if who == expected_account => Ok(who), diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index c66c718d..75f96e49 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -11,7 +11,7 @@ extern crate alloc; // Required for WASM side effects; suppress unused_crate_dependencies warning. use sp_io as _; -mod account_mapping_runtime; +mod evm_account; mod genesis_config_preset; mod orbinum_signature; mod precompiles; @@ -72,13 +72,10 @@ pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_timestamp::Call as TimestampCall; -pub use account_mapping_runtime::{ +pub use evm_account::{ evm_bytes_to_account_id_bytes, evm_h160_to_account_id, evm_h160_to_account_id_bytes, }; -use account_mapping_runtime::{ - try_evm_h160_from_account_id, AccountIdToEvmAddress, EeSuffixAddressMapping, - EnsureAddressMatches, -}; +use evm_account::{EeSuffixAddressMapping, EnsureAddressMatches}; use precompiles::FrontierPrecompiles; /// Type of block number. @@ -156,6 +153,7 @@ pub type SignedPayload = generic::SignedPayload; pub type Migrations = ( pallet_shielded_pool::migrations::v1::MigrateToV1, pallet_shielded_pool::migrations::v2::MigrateToV2, + pallet_zk_verifier::migrations::v1::MigrateToV1, ); /// Executive: handles dispatch to the various modules. @@ -204,10 +202,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: Cow::Borrowed("orbinum"), impl_name: Cow::Borrowed("orbinum"), authoring_version: 1, - spec_version: 5, + spec_version: 6, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 1, + transaction_version: 2, system_version: 1, }; @@ -485,51 +483,6 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); } -parameter_types! { - pub const AliasDeposit: Balance = 5 * 1_000_000_000_000_000_000u128; - pub const MaxAliasLength: u32 = 32; -} -pub struct PrivateLinkZkAdapter; - -impl pallet_account_mapping::PrivateLinkVerifierPort for PrivateLinkZkAdapter { - fn verify(commitment: &[u8; 32], call_hash: &[u8; 32], proof: &[u8]) -> bool { - // In benchmark builds accept any non-empty proof so the extrinsic setup/dispatch - // overhead is measured without ZK cost. The pairing computation is captured - // separately by `pallet_zk_verifier::verify_proof`. - #[cfg(feature = "runtime-benchmarks")] - { - let _ = (commitment, call_hash); - !proof.is_empty() - } - - #[cfg(not(feature = "runtime-benchmarks"))] - { - use pallet_zk_verifier::ZkVerifierPort; - pallet_zk_verifier::Pallet::::verify_private_link_proof( - proof, commitment, call_hash, None, - ) - .unwrap_or(false) - } - } -} - -impl pallet_account_mapping::Config for Runtime { - type Currency = Balances; - type AccountIdToEvmAddress = AccountIdToEvmAddress; - type AliasDeposit = AliasDeposit; - type MaxAliasLength = MaxAliasLength; - type NativeEvmChainId = ConstU32<2700>; - type WeightInfo = pallet_account_mapping::weights::SubstrateWeight; - type RuntimeCall = RuntimeCall; - type PrivateLinkVerifier = PrivateLinkZkAdapter; - - /// Secp256k1 accounts (OrbinumSignature Phase 3) have AccountId32 = [H160 | 0x00×12]. - /// Their EVM mapping is always derivable — no storage entry needed in map_account. - fn is_implicit_evm_account(account: &AccountId) -> bool { - try_evm_h160_from_account_id(account).is_some() - } -} - impl pallet_evm::Config for Runtime { type AccountProvider = pallet_evm::FrameSystemAccountProvider; type FeeCalculator = BaseFee; @@ -747,8 +700,8 @@ mod runtime { #[runtime::pallet_index(13)] pub type ShieldedPool = pallet_shielded_pool; - #[runtime::pallet_index(14)] - pub type AccountMapping = pallet_account_mapping; + // Index 14 is retired. Do not reassign: a new pallet here would make + // previously encoded calls decode as a different extrinsic. #[runtime::pallet_index(15)] pub type Authorship = pallet_authorship; @@ -857,7 +810,6 @@ mod benches { [pallet_evm_precompile_sha3fips, EVMPrecompileSha3FIPSBench::] [pallet_zk_verifier, ZkVerifier] [pallet_shielded_pool, ShieldedPool] - [pallet_account_mapping, AccountMapping] [pallet_relayer, Relayer] [pallet_validator_set, ValidatorSet] ); @@ -1401,114 +1353,6 @@ impl_runtime_apis! { } } - impl pallet_account_mapping_runtime_api::AccountMappingRuntimeApi for Runtime { - fn get_mapped_account(address: H160) -> Option { - AccountMapping::mapped_account(address) - } - - fn get_mapped_address(account: AccountId) -> Option { - AccountMapping::mapped_address(account) - } - - fn get_fallback_address(account: AccountId) -> Option { - try_evm_h160_from_account_id(&account) - } - - fn resolve_alias(alias: alloc::vec::Vec) -> Option> { - let record = pallet_account_mapping::Pallet::::runtime_api_resolve_alias(&alias)?; - Some(pallet_account_mapping_runtime_api::AliasInfo { - owner: record.owner, - evm_address: record.evm_address, - chain_links_count: record.chain_links.len() as u32, - }) - } - - fn get_alias_of(account: AccountId) -> Option> { - pallet_account_mapping::Pallet::::runtime_api_get_alias_of(account) - } - - fn get_listing_info(alias: alloc::vec::Vec) -> Option> { - pallet_account_mapping::utils::validate_alias(&alias).ok()?; - let bounded: pallet_account_mapping::pallet::AliasOf = - alias.try_into().ok()?; - let listing = pallet_account_mapping::AliasListings::::get(&bounded)?; - Some(pallet_account_mapping_runtime_api::ListingInfo { - price: listing.price, - private: listing.allowed_buyers.is_some(), - whitelist_count: listing.allowed_buyers.as_ref().map(|v| v.len() as u32).unwrap_or(0), - }) - } - - fn get_account_listing(account: AccountId) -> Option<(alloc::vec::Vec, pallet_account_mapping_runtime_api::ListingInfo)> { - let alias = pallet_account_mapping::AccountAliases::::get(&account)?; - let listing = pallet_account_mapping::AliasListings::::get(&alias)?; - let info = pallet_account_mapping_runtime_api::ListingInfo { - price: listing.price, - private: listing.allowed_buyers.is_some(), - whitelist_count: listing.allowed_buyers.as_ref().map(|v| v.len() as u32).unwrap_or(0), - }; - Some((alias.into_inner(), info)) - } - - fn can_buy(alias: alloc::vec::Vec, buyer: AccountId) -> bool { - pallet_account_mapping::Pallet::::runtime_api_can_buy(alias, buyer) - } - - fn get_full_identity(alias: alloc::vec::Vec) -> Option> { - let record = pallet_account_mapping::Pallet::::runtime_api_resolve_alias(&alias)?; - let metadata = pallet_account_mapping::AccountMetadatas::::get(&record.owner); - - Some(pallet_account_mapping_runtime_api::FullIdentityInfo { - owner: record.owner, - evm_address: record.evm_address, - chain_links: record.chain_links.into_iter().map(|l| { - pallet_account_mapping_runtime_api::ChainLink { - chain_id: l.chain_id, - address: l.address.into_inner(), - } - }).collect(), - metadata: metadata.map(|m| { - pallet_account_mapping_runtime_api::AccountMetadata { - display_name: m.display_name.map(|v| v.into_inner()), - bio: m.bio.map(|v| v.into_inner()), - avatar: m.avatar.map(|v| v.into_inner()), - } - }), - }) - } - - fn get_account_metadata(account: AccountId) -> Option { - let m = pallet_account_mapping::AccountMetadatas::::get(&account)?; - Some(pallet_account_mapping_runtime_api::AccountMetadata { - display_name: m.display_name.map(|v| v.into_inner()), - bio: m.bio.map(|v| v.into_inner()), - avatar: m.avatar.map(|v| v.into_inner()), - }) - } - - fn get_link_owner(chain_id: u32, address: alloc::vec::Vec) -> Option { - pallet_account_mapping::Pallet::::runtime_api_get_link_owner(chain_id, address) - } - - fn get_supported_chains() -> alloc::vec::Vec<(u32, pallet_account_mapping_runtime_api::SignatureScheme)> { - pallet_account_mapping::Pallet::::get_supported_chains() - } - - fn get_private_links(alias: alloc::vec::Vec) -> Option> { - pallet_account_mapping::Pallet::::runtime_api_get_private_links(alias) - .map(|links| links.into_iter() - .map(|l| pallet_account_mapping_runtime_api::PrivateLink { - chain_id: l.chain_id, - commitment: l.commitment, - }) - .collect()) - } - - fn has_private_link(alias: alloc::vec::Vec, commitment: [u8; 32]) -> bool { - pallet_account_mapping::Pallet::::runtime_api_has_private_link(alias, commitment) - } - } - // Relayer Runtime API implementation impl pallet_relayer_runtime_api::RelayerRuntimeApi for Runtime { fn is_relayer(account: sp_runtime::AccountId32) -> bool { diff --git a/template/runtime/src/orbinum_signature.rs b/template/runtime/src/orbinum_signature.rs index db16c9ca..69724dd1 100644 --- a/template/runtime/src/orbinum_signature.rs +++ b/template/runtime/src/orbinum_signature.rs @@ -29,7 +29,7 @@ use scale_info::TypeInfo; use sp_core::{ecdsa, ed25519, sr25519, H160}; use sp_runtime::traits::{IdentifyAccount, Lazy, Verify}; -use crate::account_mapping_runtime::evm_bytes_to_account_id_bytes; +use crate::evm_account::evm_bytes_to_account_id_bytes; /// The account ID type used by this runtime. pub type AccountId = sp_runtime::AccountId32; diff --git a/template/runtime/src/precompiles.rs b/template/runtime/src/precompiles.rs index ad6bdc63..261f73a8 100644 --- a/template/runtime/src/precompiles.rs +++ b/template/runtime/src/precompiles.rs @@ -4,7 +4,6 @@ use pallet_evm::{ }; use sp_core::H160; -use pallet_evm_precompile_account_mapping::AccountMappingPrecompile; use pallet_evm_precompile_balances::BalancesPrecompile; use pallet_evm_precompile_curve25519 as curve25519_precompile; use pallet_evm_precompile_modexp::Modexp; @@ -21,7 +20,7 @@ where pub fn new() -> Self { Self(Default::default()) } - pub fn used_addresses() -> [H160; 12] { + pub fn used_addresses() -> [H160; 11] { [ hash(1), hash(2), @@ -32,7 +31,6 @@ where hash(1025), hash(1026), hash(1027), - hash(2048), hash(2049), hash(2050), ] @@ -42,12 +40,10 @@ impl PrecompileSet for FrontierPrecompiles where R: pallet_evm::Config + frame_system::Config - + pallet_account_mapping::Config + pallet_shielded_pool::Config + pallet_balances::Config, ::RuntimeCall: sp_runtime::traits::Dispatchable + frame_support::dispatch::GetDispatchInfo - + From> + From>, <::RuntimeCall as sp_runtime::traits::Dispatchable>::RuntimeOrigin: From::AccountId>>, @@ -85,8 +81,6 @@ where R, crate::weights::pallet_evm_precompile_curve25519::WeightInfo, >::execute(handle)), - // Orbinum precompiles - a if a == hash(2048) => Some(AccountMappingPrecompile::::execute(handle)), a if a == hash(2049) => Some(ShieldedPoolPrecompile::::execute(handle)), a if a == hash(2050) => Some(BalancesPrecompile::::execute(handle)), _ => None, diff --git a/template/runtime/src/runtime_tests.rs b/template/runtime/src/runtime_tests.rs index 73512f94..08dac3ba 100644 --- a/template/runtime/src/runtime_tests.rs +++ b/template/runtime/src/runtime_tests.rs @@ -1,5 +1,5 @@ use crate::{ - account_mapping_runtime::EeSuffixAddressMapping, + evm_account::EeSuffixAddressMapping, orbinum_signature::{OrbinumSignature, OrbinumSigner}, AccountId, Runtime, WeightPerGas, };