From 53d4e9f3fbf55e73ea8c4a37d26c06f1fdab3280 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 11 May 2026 12:28:56 +0200 Subject: [PATCH 1/8] PR 3: Adding more call messages to accounts module --- CHANGELOG.md | 1 + .../fuzz/fuzz_targets/accounts_call_random.rs | 2 +- .../accounts_parse_call_message.rs | 6 +- .../accounts_parse_call_message_random.rs | 4 +- .../sov-accounts/README.md | 12 + .../sov-accounts/src/call.rs | 110 +++- .../sov-accounts/src/fuzz.rs | 18 +- .../sov-accounts/src/lib.rs | 12 +- .../sov-accounts/src/tests.rs | 44 +- .../sov-accounts/tests/integration/main.rs | 552 ++++++++++++++++++ .../module-schemas/schemas/sov-accounts.json | 75 +++ 11 files changed, 818 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 790ca789e0..9203576cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ # MULTISIG UPGRADE Temporary section for maintaining breaking changes from individual PRs, which will be consolidated into a single changelog entry when the feature branch is merged into `dev`. +- **Breaking change (code, wire, consensus)**: `sov_accounts::CallMessage` gains two variants, `AddCredentialToAddress { address, credential }` and `RemoveCredentialFromAddress { address, credential }`, and is now generic in `S: Spec`. The enum's wire format changes, which changes the chain hash. `AddCredentialToAddress` / `RemoveCredentialFromAddress` both require `context.sender() == address` (callers can only modify credentials on the address they are currently signing as); `InsertCredentialId` semantics are unchanged. There is no orphan guard on remove. - #2688 **Breaking change(code, state, consensus)**: The transaction formats have changed in this version. This is a consensus breaking change and requires coordinating an upgrade using `--stop-at-rollup-height`, and using sov-rollup-manager for full resyncs from this point onwards for existing rollups. This fixes Credential ID malleability that allowed the same set of signed bytes to be replayed for different credentials, across V0 and V1 (multisig) transactions and with different V1 multisig parameters. * The on-chain transaction data has changed, and multisig transactions now explicitly include the multisig id as part of the signed bytes. * Previous signatures are no longer valid. Clients will need to upgrade to our latest SDKs to be able to sign transactions in the new format. diff --git a/crates/fuzz/fuzz_targets/accounts_call_random.rs b/crates/fuzz/fuzz_targets/accounts_call_random.rs index f6cfa6498c..9a81bcdefe 100644 --- a/crates/fuzz/fuzz_targets/accounts_call_random.rs +++ b/crates/fuzz/fuzz_targets/accounts_call_random.rs @@ -11,7 +11,7 @@ use sov_test_utils::TestStorageSpec; type S = sov_test_utils::TestSpec; // Check arbitrary, random calls -fuzz_target!(|input: (&[u8], Vec<(Context, CallMessage)>)| { +fuzz_target!(|input: (&[u8], Vec<(Context, CallMessage)>)| { let storage_manager = SimpleStorageManager::::new(); let storage = storage_manager.create_storage(); let mut state = StateCheckpoint::new(storage, &MockKernel::::default(), None); diff --git a/crates/fuzz/fuzz_targets/accounts_parse_call_message.rs b/crates/fuzz/fuzz_targets/accounts_parse_call_message.rs index 267724873b..de15cfe2e4 100644 --- a/crates/fuzz/fuzz_targets/accounts_parse_call_message.rs +++ b/crates/fuzz/fuzz_targets/accounts_parse_call_message.rs @@ -3,8 +3,10 @@ use libfuzzer_sys::fuzz_target; use sov_accounts::CallMessage; -fuzz_target!(|input: CallMessage| { +type S = sov_test_utils::TestSpec; + +fuzz_target!(|input: CallMessage| { let json = serde_json::to_vec(&input).unwrap(); - let msg = serde_json::from_slice::(&json).unwrap(); + let msg = serde_json::from_slice::>(&json).unwrap(); assert_eq!(input, msg); }); diff --git a/crates/fuzz/fuzz_targets/accounts_parse_call_message_random.rs b/crates/fuzz/fuzz_targets/accounts_parse_call_message_random.rs index d49dbf8cf6..05b4c428bd 100644 --- a/crates/fuzz/fuzz_targets/accounts_parse_call_message_random.rs +++ b/crates/fuzz/fuzz_targets/accounts_parse_call_message_random.rs @@ -3,6 +3,8 @@ use libfuzzer_sys::fuzz_target; use sov_accounts::CallMessage; +type S = sov_test_utils::TestSpec; + fuzz_target!(|input: &[u8]| { - serde_json::from_slice::(input).ok(); + serde_json::from_slice::>(input).ok(); }); diff --git a/crates/module-system/module-implementations/sov-accounts/README.md b/crates/module-system/module-implementations/sov-accounts/README.md index f3cf64be8a..da95509f0c 100644 --- a/crates/module-system/module-implementations/sov-accounts/README.md +++ b/crates/module-system/module-implementations/sov-accounts/README.md @@ -12,6 +12,18 @@ addresses and records which credentials may act for which addresses. the `CallMessage::InsertCredentialId(..)` message. This writes an `account_owners` authorization. +5. It is possible to explicitly authorize a credential for the caller's own + address with `CallMessage::AddCredentialToAddress { address, credential }`, + and revoke such an authorization with + `CallMessage::RemoveCredentialFromAddress { address, credential }`. Both + calls require `message.address == context.sender()`: callers can only modify + credentials on the address they are currently signing as. The V1 signing + path's `target_address` field lets a caller signing with a credential + authorized for multiple addresses select which one to act as. There is no + orphan guard on remove — revoking the last credential leaves the address + unspendable via `account_owners`. + + ## Credential and Address Relations ### Stateless canonical address diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index 7a731cd5b5..5507f9ab4b 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -9,14 +9,40 @@ use crate::{AccountOwnerKey, Accounts}; /// Represents the available call messages for interacting with the sov-accounts module. #[derive(Debug, PartialEq, Eq, Clone, JsonSchema, UniversalWallet)] #[serialize(Borsh, Serde)] +#[schemars(bound = "S::Address: ::schemars::JsonSchema", rename = "CallMessage")] #[serde(rename_all = "snake_case")] -pub enum CallMessage { +pub enum CallMessage { /// Authorizes `credential_id` as a signer for the caller's address. /// Fails if the credential is already authorized for the caller's address. InsertCredentialId( /// The credential id being authorized. CredentialId, ), + + /// Authorizes `credential` to sign transactions that execute as `address`. + /// The caller must currently be signing as `address` (i.e. + /// `context.sender() == address`); this is naturally true after PR 2's + /// resolver for V1 `target_address = Some(address)` and for V0/target=None + /// where the signer's credential resolves into `address`. Fails if the + /// tuple is already authorized. + AddCredentialToAddress { + /// The address whose credential set is being extended. Must equal + /// `context.sender()`. + address: S::Address, + /// The credential being authorized for `address`. + credential: CredentialId, + }, + + /// Revokes `credential` from `address`. The caller must currently be + /// signing as `address`. No orphan guard: revoking the last credential + /// succeeds and leaves `address` unspendable via this map. + RemoveCredentialFromAddress { + /// The address whose credential set is being reduced. Must equal + /// `context.sender()`. + address: S::Address, + /// The credential being revoked from `address`. + credential: CredentialId, + }, } impl Accounts { @@ -26,11 +52,7 @@ impl Accounts { context: &Context, state: &mut impl TxState, ) -> anyhow::Result<()> { - if !self.enable_custom_account_mappings.get(state)?.expect( - "`enable_custom_account_mappings` should not be None; it must be set at genesis.", - ) { - bail!("Custom account mappings are disabled"); - } + self.ensure_custom_account_mappings_enabled(state)?; self.exit_if_credential_exists(&new_credential_id, context.sender(), state)?; @@ -38,6 +60,82 @@ impl Accounts { Ok(()) } + pub(crate) fn add_credential_to_address( + &mut self, + address: S::Address, + credential: CredentialId, + context: &Context, + state: &mut impl TxState, + ) -> Result<()> { + self.ensure_custom_account_mappings_enabled(state)?; + self.ensure_caller_owns(&address, context)?; + + let key = AccountOwnerKey::new(address, credential); + anyhow::ensure!( + self.account_owners + .get(&key, state) + .map_err(|err| anyhow!("Error raised while getting account owner: {err:?}"))? + .is_none(), + "CredentialId already authorized for this address" + ); + + self.authorize_credential(&address, &credential, state)?; + Ok(()) + } + + pub(crate) fn remove_credential_from_address( + &mut self, + address: S::Address, + credential: CredentialId, + context: &Context, + state: &mut impl TxState, + ) -> Result<()> { + self.ensure_custom_account_mappings_enabled(state)?; + self.ensure_caller_owns(&address, context)?; + + let key = AccountOwnerKey::new(address, credential); + anyhow::ensure!( + self.account_owners + .get(&key, state) + .map_err(|err| anyhow!("Error raised while getting account owner: {err:?}"))? + .is_some(), + "CredentialId is not authorized for this address" + ); + + self.account_owners.delete(&key, state)?; + Ok(()) + } + + fn ensure_custom_account_mappings_enabled( + &self, + state: &mut impl StateReader, + ) -> Result<()> { + if !self + .enable_custom_account_mappings + .get(state) + .map_err(|err| anyhow!("Error reading enable_custom_account_mappings: {err:?}"))? + .expect( + "`enable_custom_account_mappings` should not be None; it must be set at genesis.", + ) + { + bail!("Custom account mappings are disabled"); + } + Ok(()) + } + + /// Enforces that the caller is currently signing as `address`, i.e. + /// `context.sender() == address`. PR 2's authorizer has already proven + /// the caller controls a credential authorized for `context.sender()` + /// (either via `is_authorized` on the V1 target path, or via the + /// credential's natural resolution to `sender` on V0/target=None). + fn ensure_caller_owns(&self, address: &S::Address, context: &Context) -> Result<()> { + anyhow::ensure!( + context.sender() == address, + "Caller is not authorized to modify credentials for this address" + ); + Ok(()) + } + fn exit_if_credential_exists( &self, new_credential_id: &CredentialId, diff --git a/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs b/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs index 2abc5e43a3..e5c6f27832 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs @@ -3,9 +3,23 @@ use sov_modules_api::{CryptoSpec, DaSpec, Module, Spec, StateCheckpoint}; use crate::{Account, AccountConfig, AccountData, Accounts, CallMessage}; -impl<'a> Arbitrary<'a> for CallMessage { +impl<'a, S> Arbitrary<'a> for CallMessage +where + S: Spec, + S::Address: Arbitrary<'a>, +{ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - Ok(Self::InsertCredentialId(u.arbitrary()?)) + match u.int_in_range(0..=2)? { + 0 => Ok(Self::InsertCredentialId(u.arbitrary()?)), + 1 => Ok(Self::AddCredentialToAddress { + address: u.arbitrary()?, + credential: u.arbitrary()?, + }), + _ => Ok(Self::RemoveCredentialFromAddress { + address: u.arbitrary()?, + credential: u.arbitrary()?, + }), + } } } diff --git a/crates/module-system/module-implementations/sov-accounts/src/lib.rs b/crates/module-system/module-implementations/sov-accounts/src/lib.rs index 5a9c452091..512c42d8a7 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/lib.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/lib.rs @@ -120,7 +120,7 @@ impl Module for Accounts { type Config = AccountConfig; - type CallMessage = call::CallMessage; + type CallMessage = call::CallMessage; type Event = (); @@ -143,8 +143,16 @@ impl Module for Accounts { ) -> Result<(), Self::Error> { match msg { call::CallMessage::InsertCredentialId(new_credential_id) => { - Ok(self.insert_credential_id(new_credential_id, context, state)?) + self.insert_credential_id(new_credential_id, context, state) } + call::CallMessage::AddCredentialToAddress { + address, + credential, + } => self.add_credential_to_address(address, credential, context, state), + call::CallMessage::RemoveCredentialFromAddress { + address, + credential, + } => self.remove_credential_from_address(address, credential, context, state), } } } diff --git a/crates/module-system/module-implementations/sov-accounts/src/tests.rs b/crates/module-system/module-implementations/sov-accounts/src/tests.rs index a7c975cab4..a6781424f7 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/tests.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/tests.rs @@ -7,14 +7,50 @@ use crate::CallMessage; fn test_display_accounts_call() { #[derive(Debug, Clone, PartialEq, borsh::BorshSerialize, UniversalWallet)] enum RuntimeCall { - Accounts(CallMessage), + Accounts(CallMessage), } - let msg = RuntimeCall::Accounts(CallMessage::InsertCredentialId([1; 32].into())); - + let insert_msg = RuntimeCall::Accounts(CallMessage::InsertCredentialId([1; 32].into())); let schema = Schema::of_single_type::().unwrap(); assert_eq!( r#"Accounts.InsertCredentialId(0x0101010101010101010101010101010101010101010101010101010101010101)"#, - schema.display(0, &borsh::to_vec(&msg).unwrap()).unwrap(), + schema + .display(0, &borsh::to_vec(&insert_msg).unwrap()) + .unwrap(), + ); + + let addr_bytes: [u8; 28] = core::array::from_fn(|i| (i + 1) as u8); + let address = ::Address::from(addr_bytes); + + let add_msg = RuntimeCall::Accounts(CallMessage::AddCredentialToAddress { + address, + credential: [2; 32].into(), + }); + let rendered = schema + .display(0, &borsh::to_vec(&add_msg).unwrap()) + .unwrap(); + assert!( + rendered.starts_with("Accounts.AddCredentialToAddress"), + "unexpected render: {rendered}" + ); + assert!( + rendered.contains("0x0202020202020202020202020202020202020202020202020202020202020202"), + "render missing credential: {rendered}" + ); + + let remove_msg = RuntimeCall::Accounts(CallMessage::RemoveCredentialFromAddress { + address, + credential: [3; 32].into(), + }); + let rendered = schema + .display(0, &borsh::to_vec(&remove_msg).unwrap()) + .unwrap(); + assert!( + rendered.starts_with("Accounts.RemoveCredentialFromAddress"), + "unexpected render: {rendered}" + ); + assert!( + rendered.contains("0x0303030303030303030303030303030303030303030303030303030303030303"), + "render missing credential: {rendered}" ); } diff --git a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs index 6c112784ca..32a71b1b56 100644 --- a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs +++ b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs @@ -535,6 +535,22 @@ fn make_v1_tx( .to_multisig_tx(multisig.clone()) } +/// Builds an unsigned V1 tx carrying the given accounts call. +fn make_v1_tx_with_call( + multisig: &sov_modules_api::Multisig<<::CryptoSpec as CryptoSpec>::PublicKey>, + call: CallMessage, + target_address: Option<::Address>, + generation: u64, +) -> Version1 { + UnsignedTransactionV0::::new_with_details( + TestAccountsRuntimeCall::Accounts(call), + sov_modules_api::capabilities::UniquenessData::Generation(generation), + default_test_tx_details::(), + target_address, + ) + .to_multisig_tx(multisig.clone()) +} + fn make_v0_tx( sender: &TestUser, inner_credential: sov_modules_api::CredentialId, @@ -979,3 +995,539 @@ fn test_v0_address_override_tamper_breaks_signature() { }), }); } + +/// An existing owner can authorize a second credential for their own address +/// via `AddCredentialToAddress` (V0 path, sender == address). +#[test] +fn test_add_credential_to_address_by_owner() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let new_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>(CallMessage::AddCredentialToAddress { + address: owner_address, + credential: new_credential, + }), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!( + accounts + .is_authorized(&owner_address, &new_credential, state) + .unwrap(), + "new credential should be authorized for owner's address" + ); + }), + }); +} + +/// A brand-new user (no genesis entry, no prior `account_owners` entry) can +/// authorize a credential for their own default address. The simple +/// `sender == address` rule subsumes the bootstrap case: `sender` is the +/// user's default address because the resolver returns the default for an +/// unknown credential. +#[test] +fn test_add_credential_to_address_bootstrap() { + let ( + TestData { + non_registered_account: fresh_user, + .. + }, + mut runner, + ) = setup(); + let fresh_address = fresh_user.address(); + let extra_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: fresh_user.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: fresh_address, + credential: extra_credential, + }, + ), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized(&fresh_address, &extra_credential, state) + .unwrap()); + }), + }); +} + +/// A caller cannot add a credential for an address that isn't their own. +/// Under the resolver, `context.sender()` is the caller's resolved address; +/// the handler rejects if `message.address != context.sender()`. Uses two +/// users whose canonical (`hash(pubkey).into()`) addresses equal their funded +/// addresses so V0 gas reservation succeeds and the handler actually runs. +#[test] +fn test_add_credential_to_address_non_owner_rejected() { + let attacker = TestUser::::generate_with_default_balance(); + let victim = TestUser::::generate_with_default_balance(); + let victim_address = victim.address(); + + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![attacker.clone(), victim]); + let genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + let attacker_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: attacker.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: victim_address, + credential: attacker_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Caller is not authorized to modify credentials for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + // Confirm the attacker's credential was never written under the victim. + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized(&victim_address, &attacker_credential, state) + .unwrap()); + }), + }); +} + +/// Adding a tuple that already exists fails cleanly. +#[test] +fn test_add_credential_duplicate_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential, + }, + )); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>(CallMessage::AddCredentialToAddress { + address: owner_address, + credential, + }), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId already authorized for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); +} + +/// The owner can revoke a credential from their own address. +#[test] +fn test_remove_credential_from_address_by_owner() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let kept_credential = TestPrivateKey::generate().pub_key().credential_id(); + let removed_credential = TestPrivateKey::generate().pub_key().credential_id(); + + // Authorize both credentials for the owner. + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: kept_credential, + }, + )); + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: removed_credential, + }, + )); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RemoveCredentialFromAddress { + address: owner_address, + credential: removed_credential, + }, + ), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized(&owner_address, &removed_credential, state) + .unwrap()); + assert!( + accounts + .is_authorized(&owner_address, &kept_credential, state) + .unwrap(), + "unrelated authorization should survive the revocation" + ); + }), + }); +} + +/// A caller cannot revoke a credential from an address they don't own. Uses +/// two canonical-address users (no custom credential_id) so the V0 gas path +/// resolves correctly to the funded address. +#[test] +fn test_remove_credential_non_owner_rejected() { + let attacker = TestUser::::generate_with_default_balance(); + let victim = TestUser::::generate_with_default_balance(); + let victim_address = victim.address(); + let victim_credential = victim.credential_id(); + + // Seed victim's `(address, credential)` so the attacker's revoke attempt + // targets a real tuple. Otherwise the handler would fail on the tuple + // check before the ownership check, hiding the bug we care about. + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![attacker.clone(), victim]); + let mut genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + genesis.accounts.accounts.push(AccountData { + credential_id: victim_credential, + address: victim_address, + }); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + runner.execute_transaction(TransactionTestCase { + input: attacker.create_plain_message::>( + CallMessage::RemoveCredentialFromAddress { + address: victim_address, + credential: victim_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Caller is not authorized to modify credentials for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + // Victim's authorization is unaffected. + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized(&victim_address, &victim_credential, state) + .unwrap()); + }), + }); +} + +/// Attempting to remove a tuple that doesn't exist fails cleanly. +#[test] +fn test_remove_credential_not_authorized_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let ghost_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RemoveCredentialFromAddress { + address: owner_address, + credential: ghost_credential, + }, + ), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId is not authorized for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); +} + +/// Removing the last credential succeeds (no orphan guard) and leaves the +/// address unspendable via `account_owners`: a subsequent V1 tx with +/// `target_address = Some(orphaned)` signed by the revoked credential is +/// skipped at the resolver. +#[test] +fn test_remove_last_credential_orphans_address() { + let MultisigEnv { + keys, + multisig, + credential_id: multisig_credential_id, + user: multisig_user, + } = make_multisig_env(); + let alice = TestUser::::generate_with_default_balance(); + let alice_address = alice.address(); + + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![multisig_user, alice]); + let mut genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + // Seed `(alice_address, multisig_credential_id)` so the multisig can route + // to Alice's address before we revoke. + genesis.accounts.accounts.push(AccountData { + credential_id: multisig_credential_id, + address: alice_address, + }); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + // Revoke the only credential authorizing the multisig for Alice. + let mut revoke_tx = make_v1_tx_with_call( + &multisig, + CallMessage::RemoveCredentialFromAddress { + address: alice_address, + credential: multisig_credential_id, + }, + Some(alice_address), + 0, + ); + sign_v1(&mut revoke_tx, &keys[0]); + sign_v1(&mut revoke_tx, &keys[1]); + + runner.execute_transaction(TransactionTestCase { + input: submit_v1(revoke_tx), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized(&alice_address, &multisig_credential_id, state) + .unwrap()); + }), + }); + + // Orphan confirmed: trying to route to Alice again is rejected at the + // resolver (skipped, not reverted). + let follow_up_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut orphan_tx = make_v1_tx_with_call( + &multisig, + CallMessage::InsertCredentialId(follow_up_credential), + Some(alice_address), + 1, + ); + sign_v1(&mut orphan_tx, &keys[0]); + sign_v1(&mut orphan_tx, &keys[1]); + + runner.execute_transaction(TransactionTestCase { + input: submit_v1(orphan_tx), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Skipped(SkippedTxContents { error, .. }) => { + let msg = error.to_string(); + assert!( + msg.contains("not authorized for target address"), + "expected resolver skip; got: {msg}" + ); + } + other => panic!("expected skipped tx, got {other:?}"), + }), + }); +} + +/// End-to-end multisig rotation M1 → M2 while preserving the original +/// address. Exercises `AddCredentialToAddress` + `RemoveCredentialFromAddress` +/// via V1 + `target_address`. +#[test] +fn test_multisig_key_rotation() { + use sov_modules_api::Multisig; + + // M1 is the incumbent 2-of-3 multisig. M2 is the rotated key set, fully + // disjoint so credential_id_1 and credential_id_2 cannot collide. + let keys_1 = [ + TestPrivateKey::generate(), + TestPrivateKey::generate(), + TestPrivateKey::generate(), + ]; + let keys_2 = [ + TestPrivateKey::generate(), + TestPrivateKey::generate(), + TestPrivateKey::generate(), + ]; + let multisig_1 = Multisig::new(2, keys_1.iter().map(|k| k.pub_key()).collect()); + let multisig_2 = Multisig::new(2, keys_2.iter().map(|k| k.pub_key()).collect()); + let credential_id_1 = + multisig_1.credential_id::<<::CryptoSpec as CryptoSpec>::Hasher>(); + let credential_id_2 = + multisig_2.credential_id::<<::CryptoSpec as CryptoSpec>::Hasher>(); + + let multisig_user = + TestUser::generate_with_default_balance().add_credential_id(credential_id_1); + let target_address = multisig_user.address(); + + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![multisig_user]); + let genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + // 1. M1 authorizes M2 for the shared address X. + let mut add_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::AddCredentialToAddress { + address: target_address, + credential: credential_id_2, + }, + Some(target_address), + 0, + ); + sign_v1(&mut add_tx, &keys_1[0]); + sign_v1(&mut add_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(add_tx), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized(&target_address, &credential_id_1, state) + .unwrap()); + assert!(accounts + .is_authorized(&target_address, &credential_id_2, state) + .unwrap()); + }), + }); + + // 2. M1 revokes itself from X. + let mut remove_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::RemoveCredentialFromAddress { + address: target_address, + credential: credential_id_1, + }, + Some(target_address), + 1, + ); + sign_v1(&mut remove_tx, &keys_1[0]); + sign_v1(&mut remove_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(remove_tx), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized(&target_address, &credential_id_1, state) + .unwrap()); + assert!(accounts + .is_authorized(&target_address, &credential_id_2, state) + .unwrap()); + }), + }); + + // 3. M2 signs a routine tx targeting X — should succeed. + let follow_up_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut m2_tx = make_v1_tx_with_call( + &multisig_2, + CallMessage::InsertCredentialId(follow_up_credential), + Some(target_address), + 0, + ); + sign_v1(&mut m2_tx, &keys_2[0]); + sign_v1(&mut m2_tx, &keys_2[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(m2_tx), + assert: Box::new(move |result, _state| { + assert!( + result.tx_receipt.is_successful(), + "M2 should now control X; got {:?}", + result.tx_receipt + ); + }), + }); + + // 4. M1 signs another tx targeting X — should be skipped (no longer + // authorized). + let orphan_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut m1_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::InsertCredentialId(orphan_credential), + Some(target_address), + 2, + ); + sign_v1(&mut m1_tx, &keys_1[0]); + sign_v1(&mut m1_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(m1_tx), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Skipped(SkippedTxContents { error, .. }) => { + let msg = error.to_string(); + assert!( + msg.contains("not authorized for target address"), + "expected resolver skip after rotation; got: {msg}" + ); + } + other => panic!("expected skipped tx after rotation, got {other:?}"), + }), + }); +} + +/// With `enable_custom_account_mappings = false`, both new call variants are +/// rejected by the same guard that already rejects `InsertCredentialId`. +#[test] +fn test_feature_flag_gates_new_variants() { + let (user, mut runner) = setup_with_disable_custom_account_mappings(); + let user_address = user.address(); + let credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: user.create_plain_message::>(CallMessage::AddCredentialToAddress { + address: user_address, + credential, + }), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Custom account mappings are disabled" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); + + runner.execute_transaction(TransactionTestCase { + input: user.create_plain_message::>( + CallMessage::RemoveCredentialFromAddress { + address: user_address, + credential, + }, + ), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Custom account mappings are disabled" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); +} diff --git a/crates/module-system/module-schemas/schemas/sov-accounts.json b/crates/module-system/module-schemas/schemas/sov-accounts.json index 629b469441..e00152e75b 100644 --- a/crates/module-system/module-schemas/schemas/sov-accounts.json +++ b/crates/module-system/module-schemas/schemas/sov-accounts.json @@ -15,9 +15,84 @@ } }, "additionalProperties": false + }, + { + "description": "Authorizes `credential` to sign transactions that execute as `address`. The caller must currently be signing as `address` (i.e. `context.sender() == address`); this is naturally true after PR 2's resolver for V1 `target_address = Some(address)` and for V0/target=None where the signer's credential resolves into `address`. Fails if the tuple is already authorized.", + "type": "object", + "required": [ + "add_credential_to_address" + ], + "properties": { + "add_credential_to_address": { + "type": "object", + "required": [ + "address", + "credential" + ], + "properties": { + "address": { + "description": "The address whose credential set is being extended. Must equal `context.sender()`.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "credential": { + "description": "The credential being authorized for `address`.", + "allOf": [ + { + "$ref": "#/definitions/CredentialId" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Revokes `credential` from `address`. The caller must currently be signing as `address`. No orphan guard: revoking the last credential succeeds and leaves `address` unspendable via this map.", + "type": "object", + "required": [ + "remove_credential_from_address" + ], + "properties": { + "remove_credential_from_address": { + "type": "object", + "required": [ + "address", + "credential" + ], + "properties": { + "address": { + "description": "The address whose credential set is being reduced. Must equal `context.sender()`.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "credential": { + "description": "The credential being revoked from `address`.", + "allOf": [ + { + "$ref": "#/definitions/CredentialId" + } + ] + } + } + } + }, + "additionalProperties": false } ], "definitions": { + "Address": { + "description": "Address", + "type": "string", + "pattern": "^sov1[a-zA-Z0-9]+$" + }, "CredentialId": { "description": "32 bytes in hexadecimal format, with `0x` prefix.", "type": "string", From f24f82f81a4f73160f2f57cd589a78d9d5c968f8 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 11 May 2026 12:30:42 +0200 Subject: [PATCH 2/8] Adding swap credential id --- CHANGELOG.md | 2 +- .../fuzz/fuzz_targets/accounts_call_random.rs | 3 +- .../sov-accounts/README.md | 28 +- .../sov-accounts/src/call.rs | 110 +- .../sov-accounts/src/capabilities.rs | 9 +- .../sov-accounts/src/fuzz.rs | 9 +- .../sov-accounts/src/lib.rs | 20 +- .../sov-accounts/src/tests.rs | 25 + .../sov-accounts/tests/integration/main.rs | 427 +- .../module-schemas/schemas/sov-accounts.json | 46 +- .../sov-address/src/evm/address.rs | 41 +- crates/module-system/sov-address/src/lib.rs | 18 +- .../module-system/sov-capabilities/src/lib.rs | 27 +- examples/demo-rollup/demo-rollup-schema.bin | Bin 7921 -> 13826 bytes examples/demo-rollup/demo-rollup-schema.json | 4356 ++++++++++++---- .../stf/stf-declaration/src/address.rs | 10 +- .../__fixtures__/demo-rollup-schema.json | 4402 +++++++++++++---- .../serializers/src/serializer.test.ts | 85 +- .../packages/serializers/src/serializer.ts | 14 +- .../packages/signers/src/wasm/eip712.test.ts | 4 +- .../tests/schema.test.ts | 40 +- 21 files changed, 7651 insertions(+), 2025 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9203576cb8..165d160a92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ # MULTISIG UPGRADE Temporary section for maintaining breaking changes from individual PRs, which will be consolidated into a single changelog entry when the feature branch is merged into `dev`. -- **Breaking change (code, wire, consensus)**: `sov_accounts::CallMessage` gains two variants, `AddCredentialToAddress { address, credential }` and `RemoveCredentialFromAddress { address, credential }`, and is now generic in `S: Spec`. The enum's wire format changes, which changes the chain hash. `AddCredentialToAddress` / `RemoveCredentialFromAddress` both require `context.sender() == address` (callers can only modify credentials on the address they are currently signing as); `InsertCredentialId` semantics are unchanged. There is no orphan guard on remove. +- **Breaking change (code, wire, consensus)**: `sov_accounts::CallMessage` gains three variants — `AddCredentialToAddress { address, credential }`, `RemoveCredentialFromAddress { address, credential }`, and `RotateCredentialOnAddress { address, old_credential, new_credential }` — and is now generic in `S: Spec`. The enum's wire format changes, which changes the chain hash. All three new variants require `context.sender() == address` (callers can only modify credentials on the address they are currently signing as); `InsertCredentialId` semantics are unchanged. `Rotate` is functionally equivalent to a `Remove` + `Add` collapsed into a single call for key rotation. There is no orphan guard on remove. - #2688 **Breaking change(code, state, consensus)**: The transaction formats have changed in this version. This is a consensus breaking change and requires coordinating an upgrade using `--stop-at-rollup-height`, and using sov-rollup-manager for full resyncs from this point onwards for existing rollups. This fixes Credential ID malleability that allowed the same set of signed bytes to be replayed for different credentials, across V0 and V1 (multisig) transactions and with different V1 multisig parameters. * The on-chain transaction data has changed, and multisig transactions now explicitly include the multisig id as part of the signed bytes. * Previous signatures are no longer valid. Clients will need to upgrade to our latest SDKs to be able to sign transactions in the new format. diff --git a/crates/fuzz/fuzz_targets/accounts_call_random.rs b/crates/fuzz/fuzz_targets/accounts_call_random.rs index 9a81bcdefe..43997b82ba 100644 --- a/crates/fuzz/fuzz_targets/accounts_call_random.rs +++ b/crates/fuzz/fuzz_targets/accounts_call_random.rs @@ -9,9 +9,10 @@ use sov_test_utils::storage::SimpleStorageManager; use sov_test_utils::TestStorageSpec; type S = sov_test_utils::TestSpec; +type FuzzInput<'a> = (&'a [u8], Vec<(Context, CallMessage)>); // Check arbitrary, random calls -fuzz_target!(|input: (&[u8], Vec<(Context, CallMessage)>)| { +fuzz_target!(|input: FuzzInput| { let storage_manager = SimpleStorageManager::::new(); let storage = storage_manager.create_storage(); let mut state = StateCheckpoint::new(storage, &MockKernel::::default(), None); diff --git a/crates/module-system/module-implementations/sov-accounts/README.md b/crates/module-system/module-implementations/sov-accounts/README.md index da95509f0c..eaeec489bd 100644 --- a/crates/module-system/module-implementations/sov-accounts/README.md +++ b/crates/module-system/module-implementations/sov-accounts/README.md @@ -14,14 +14,16 @@ addresses and records which credentials may act for which addresses. 5. It is possible to explicitly authorize a credential for the caller's own address with `CallMessage::AddCredentialToAddress { address, credential }`, - and revoke such an authorization with - `CallMessage::RemoveCredentialFromAddress { address, credential }`. Both - calls require `message.address == context.sender()`: callers can only modify - credentials on the address they are currently signing as. The V1 signing - path's `target_address` field lets a caller signing with a credential - authorized for multiple addresses select which one to act as. There is no - orphan guard on remove — revoking the last credential leaves the address - unspendable via `account_owners`. + revoke such an authorization with + `CallMessage::RemoveCredentialFromAddress { address, credential }`, and + atomically swap one credential for another with + `CallMessage::RotateCredentialOnAddress { address, old_credential, new_credential }`. + All three calls require `message.address == context.sender()`: callers can + only modify credentials on the address they are currently signing as. The + V1 signing path's `target_address` field lets a caller signing with a + credential authorized for multiple addresses select which one to act as. + There is no orphan guard on remove — revoking the last credential leaves + the address unspendable via `account_owners`. ## Credential and Address Relations @@ -38,12 +40,14 @@ If a credential has no explicit authorization, this canonical address is the nat ### Account-credential authorization map ```text -account_owners[(address, credential_id)] = true +account_owners[(address, credential_id)] = true | false ``` -This state map records authorization. -A present entry means the credential is authorized to sign transactions that execute as the given address. -The key is the exact `(address, credential_id)` pair, so this relation does not provide a credential-only lookup by itself. +This state map records authorization overrides. `true` means the credential is authorized to sign transactions that execute as the given address. +`false` +explicitly revokes fallback authorization for that pair, including stateless +canonical fallback. The key is the exact `(address, credential_id)` pair, so +this relation does not provide a credential-only lookup by itself. This relation answers "may this credential act as this address?" once the target address is known. New `InsertCredentialId` calls write this relation. diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index 5507f9ab4b..bd4f53fb81 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context as _}; +use anyhow::{anyhow, bail, Context as _}; use schemars::JsonSchema; use sov_modules_api::macros::{serialize, UniversalWallet}; use sov_modules_api::{Context, CredentialId, Spec, StateReader, TxState}; @@ -20,11 +20,8 @@ pub enum CallMessage { ), /// Authorizes `credential` to sign transactions that execute as `address`. - /// The caller must currently be signing as `address` (i.e. - /// `context.sender() == address`); this is naturally true after PR 2's - /// resolver for V1 `target_address = Some(address)` and for V0/target=None - /// where the signer's credential resolves into `address`. Fails if the - /// tuple is already authorized. + /// The caller must currently be signing as `address`. Fails if the tuple + /// is already authorized. AddCredentialToAddress { /// The address whose credential set is being extended. Must equal /// `context.sender()`. @@ -43,6 +40,23 @@ pub enum CallMessage { /// The credential being revoked from `address`. credential: CredentialId, }, + + /// Atomically swaps `old_credential` for `new_credential` on `address`. + /// Functionally equivalent to a `RemoveCredentialFromAddress` followed by + /// an `AddCredentialToAddress`, collapsed into a single call so the + /// caller does not have to authorize two transactions during a rotation. + /// The caller must currently be signing as `address`. + RotateCredentialOnAddress { + /// The address whose credential set is being rotated. Must equal + /// `context.sender()`. + address: S::Address, + /// The credential being revoked from `address`. Must be currently + /// authorized. + old_credential: CredentialId, + /// The credential being authorized for `address`. Must not already + /// be authorized. + new_credential: CredentialId, + }, } impl Accounts { @@ -54,7 +68,7 @@ impl Accounts { ) -> anyhow::Result<()> { self.ensure_custom_account_mappings_enabled(state)?; - self.exit_if_credential_exists(&new_credential_id, context.sender(), state)?; + self.ensure_credential_not_authorized(context.sender(), &new_credential_id, state)?; self.authorize_credential(context.sender(), &new_credential_id, state)?; Ok(()) @@ -66,18 +80,10 @@ impl Accounts { credential: CredentialId, context: &Context, state: &mut impl TxState, - ) -> Result<()> { + ) -> anyhow::Result<()> { self.ensure_custom_account_mappings_enabled(state)?; self.ensure_caller_owns(&address, context)?; - - let key = AccountOwnerKey::new(address, credential); - anyhow::ensure!( - self.account_owners - .get(&key, state) - .map_err(|err| anyhow!("Error raised while getting account owner: {err:?}"))? - .is_none(), - "CredentialId already authorized for this address" - ); + self.ensure_credential_not_authorized(&address, &credential, state)?; self.authorize_credential(&address, &credential, state)?; Ok(()) @@ -89,27 +95,60 @@ impl Accounts { credential: CredentialId, context: &Context, state: &mut impl TxState, - ) -> Result<()> { + ) -> anyhow::Result<()> { self.ensure_custom_account_mappings_enabled(state)?; self.ensure_caller_owns(&address, context)?; - let key = AccountOwnerKey::new(address, credential); anyhow::ensure!( - self.account_owners - .get(&key, state) - .map_err(|err| anyhow!("Error raised while getting account owner: {err:?}"))? - .is_some(), + self.is_authorized_for(&address, &credential, state) + .map_err(|err| anyhow!("Error raised while checking authorization: {err:?}"))?, "CredentialId is not authorized for this address" ); - self.account_owners.delete(&key, state)?; + self.revoke_credential(&address, &credential, state)?; + Ok(()) + } + + pub(crate) fn rotate_credential_on_address( + &mut self, + address: S::Address, + old_credential: CredentialId, + new_credential: CredentialId, + context: &Context, + state: &mut impl TxState, + ) -> anyhow::Result<()> { + self.ensure_custom_account_mappings_enabled(state)?; + self.ensure_caller_owns(&address, context)?; + + anyhow::ensure!( + self.is_authorized_for(&address, &old_credential, state) + .map_err(|err| anyhow!("Error raised while checking authorization: {err:?}"))?, + "CredentialId is not authorized for this address" + ); + self.ensure_credential_not_authorized(&address, &new_credential, state)?; + + self.revoke_credential(&address, &old_credential, state)?; + self.authorize_credential(&address, &new_credential, state)?; + Ok(()) + } + + fn revoke_credential( + &mut self, + address: &S::Address, + credential: &CredentialId, + state: &mut impl TxState, + ) -> anyhow::Result<()> { + let key = AccountOwnerKey::new(*address, *credential); + // Write `false` explicitly so the canonical-address fallback in + // `is_authorized_for` cannot re-authorize the tuple. + self.account_owners.set(&key, &false, state)?; Ok(()) } fn ensure_custom_account_mappings_enabled( &self, state: &mut impl StateReader, - ) -> Result<()> { + ) -> anyhow::Result<()> { if !self .enable_custom_account_mappings .get(state) @@ -123,12 +162,10 @@ impl Accounts { Ok(()) } - /// Enforces that the caller is currently signing as `address`, i.e. - /// `context.sender() == address`. PR 2's authorizer has already proven - /// the caller controls a credential authorized for `context.sender()` - /// (either via `is_authorized` on the V1 target path, or via the - /// credential's natural resolution to `sender` on V0/target=None). - fn ensure_caller_owns(&self, address: &S::Address, context: &Context) -> Result<()> { + /// Enforces that the caller is signing as `address`. The upstream + /// authorization path has already verified the caller controls a + /// credential authorized for `context.sender()`. + fn ensure_caller_owns(&self, address: &S::Address, context: &Context) -> anyhow::Result<()> { anyhow::ensure!( context.sender() == address, "Caller is not authorized to modify credentials for this address" @@ -136,17 +173,16 @@ impl Accounts { Ok(()) } - fn exit_if_credential_exists( + fn ensure_credential_not_authorized( &self, - new_credential_id: &CredentialId, address: &S::Address, + credential: &CredentialId, state: &mut impl StateReader, ) -> anyhow::Result<()> { anyhow::ensure!( - self.account_owners - .get(&AccountOwnerKey::new(*address, *new_credential_id), state) - .context("Failed to read account owner")? - .is_none(), + !self + .is_explicitly_authorized(address, credential, state) + .context("Failed to read account owner")?, "CredentialId already authorized for this address" ); Ok(()) diff --git a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs index 7128610e4b..681f2c6fb5 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs @@ -30,7 +30,7 @@ impl Accounts { Ok(self .account_owners .get(&AccountOwnerKey::new(*address, *credential_id), state)? - .is_some()) + .unwrap_or(false)) } /// Returns `true` if `credential_id` is authorized to act as `address`. @@ -44,6 +44,13 @@ impl Accounts { credential_id: &CredentialId, state: &mut ST, ) -> Result { + if let Some(is_authorized) = self + .account_owners + .get(&AccountOwnerKey::new(*address, *credential_id), state)? + { + return Ok(is_authorized); + } + let canonical_address: S::Address = (*credential_id).into(); if canonical_address == *address { return Ok(true); diff --git a/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs b/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs index e5c6f27832..8e6ac3d2c3 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/fuzz.rs @@ -9,16 +9,21 @@ where S::Address: Arbitrary<'a>, { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { - match u.int_in_range(0..=2)? { + match u.int_in_range(0..=3)? { 0 => Ok(Self::InsertCredentialId(u.arbitrary()?)), 1 => Ok(Self::AddCredentialToAddress { address: u.arbitrary()?, credential: u.arbitrary()?, }), - _ => Ok(Self::RemoveCredentialFromAddress { + 2 => Ok(Self::RemoveCredentialFromAddress { address: u.arbitrary()?, credential: u.arbitrary()?, }), + _ => Ok(Self::RotateCredentialOnAddress { + address: u.arbitrary()?, + old_credential: u.arbitrary()?, + new_credential: u.arbitrary()?, + }), } } } diff --git a/crates/module-system/module-implementations/sov-accounts/src/lib.rs b/crates/module-system/module-implementations/sov-accounts/src/lib.rs index 512c42d8a7..bced7e0fdd 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/lib.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/lib.rs @@ -39,9 +39,7 @@ pub struct Account { pub addr: S::Address, } -/// Composite key for [`Accounts::account_owners`]. A present entry -/// `(address, credential_id)` means `credential_id` is authorized to sign -/// transactions that execute as `address`. +/// Composite key for [`Accounts::account_owners`]. #[derive( borsh::BorshDeserialize, borsh::BorshSerialize, Debug, Clone, Copy, PartialEq, Eq, Hash, )] @@ -109,8 +107,9 @@ pub struct Accounts { #[state] enable_custom_account_mappings: StateValue, - /// Authorization set: a present entry means `credential_id` may sign as - /// `address`. + /// Authorization overrides. `Some(true)` means `credential_id` may sign as + /// `address`; `Some(false)` explicitly revokes fallback authorization for + /// the pair. #[state] pub(crate) account_owners: StateMap, bool>, } @@ -153,6 +152,17 @@ impl Module for Accounts { address, credential, } => self.remove_credential_from_address(address, credential, context, state), + call::CallMessage::RotateCredentialOnAddress { + address, + old_credential, + new_credential, + } => self.rotate_credential_on_address( + address, + old_credential, + new_credential, + context, + state, + ), } } } diff --git a/crates/module-system/module-implementations/sov-accounts/src/tests.rs b/crates/module-system/module-implementations/sov-accounts/src/tests.rs index a6781424f7..8b612b42f4 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/tests.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/tests.rs @@ -1,10 +1,14 @@ use sov_modules_api::prelude::*; use sov_modules_api::sov_universal_wallet::schema::Schema; +use sov_modules_api::Spec; +use sov_test_utils::TestSpec; use crate::CallMessage; #[test] fn test_display_accounts_call() { + type S = TestSpec; + #[derive(Debug, Clone, PartialEq, borsh::BorshSerialize, UniversalWallet)] enum RuntimeCall { Accounts(CallMessage), @@ -53,4 +57,25 @@ fn test_display_accounts_call() { rendered.contains("0x0303030303030303030303030303030303030303030303030303030303030303"), "render missing credential: {rendered}" ); + + let rotate_msg = RuntimeCall::Accounts(CallMessage::RotateCredentialOnAddress { + address, + old_credential: [4; 32].into(), + new_credential: [5; 32].into(), + }); + let rendered = schema + .display(0, &borsh::to_vec(&rotate_msg).unwrap()) + .unwrap(); + assert!( + rendered.starts_with("Accounts.RotateCredentialOnAddress"), + "unexpected render: {rendered}" + ); + assert!( + rendered.contains("0x0404040404040404040404040404040404040404040404040404040404040404"), + "render missing old_credential: {rendered}" + ); + assert!( + rendered.contains("0x0505050505050505050505050505050505050505050505050505050505050505"), + "render missing new_credential: {rendered}" + ); } diff --git a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs index 32a71b1b56..6b642113ed 100644 --- a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs +++ b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs @@ -539,14 +539,14 @@ fn make_v1_tx( fn make_v1_tx_with_call( multisig: &sov_modules_api::Multisig<<::CryptoSpec as CryptoSpec>::PublicKey>, call: CallMessage, - target_address: Option<::Address>, + address_override: Option<::Address>, generation: u64, ) -> Version1 { UnsignedTransactionV0::::new_with_details( TestAccountsRuntimeCall::Accounts(call), sov_modules_api::capabilities::UniquenessData::Generation(generation), default_test_tx_details::(), - target_address, + address_override, ) .to_multisig_tx(multisig.clone()) } @@ -1020,7 +1020,7 @@ fn test_add_credential_to_address_by_owner() { let accounts = Accounts::::default(); assert!( accounts - .is_authorized(&owner_address, &new_credential, state) + .is_authorized_for(&owner_address, &new_credential, state) .unwrap(), "new credential should be authorized for owner's address" ); @@ -1056,7 +1056,7 @@ fn test_add_credential_to_address_bootstrap() { assert!(result.tx_receipt.is_successful()); let accounts = Accounts::::default(); assert!(accounts - .is_authorized(&fresh_address, &extra_credential, state) + .is_authorized_for(&fresh_address, &extra_credential, state) .unwrap()); }), }); @@ -1101,7 +1101,7 @@ fn test_add_credential_to_address_non_owner_rejected() { // Confirm the attacker's credential was never written under the victim. let accounts = Accounts::::default(); assert!(!accounts - .is_authorized(&victim_address, &attacker_credential, state) + .is_authorized_for(&victim_address, &attacker_credential, state) .unwrap()); }), }); @@ -1183,11 +1183,11 @@ fn test_remove_credential_from_address_by_owner() { assert!(result.tx_receipt.is_successful()); let accounts = Accounts::::default(); assert!(!accounts - .is_authorized(&owner_address, &removed_credential, state) + .is_authorized_for(&owner_address, &removed_credential, state) .unwrap()); assert!( accounts - .is_authorized(&owner_address, &kept_credential, state) + .is_authorized_for(&owner_address, &kept_credential, state) .unwrap(), "unrelated authorization should survive the revocation" ); @@ -1195,6 +1195,37 @@ fn test_remove_credential_from_address_by_owner() { }); } +/// Removing a credential from its own canonical address writes an explicit +/// denial, because there is no legacy map entry to delete for that fallback. +#[test] +fn test_remove_canonical_credential_blocks_fallback_authorization() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let owner_credential = owner.credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RemoveCredentialFromAddress { + address: owner_address, + credential: owner_credential, + }, + ), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized_for(&owner_address, &owner_credential, state) + .unwrap()); + }), + }); +} + /// A caller cannot revoke a credential from an address they don't own. Uses /// two canonical-address users (no custom credential_id) so the V0 gas path /// resolves correctly to the funded address. @@ -1238,7 +1269,7 @@ fn test_remove_credential_non_owner_rejected() { // Victim's authorization is unaffected. let accounts = Accounts::::default(); assert!(accounts - .is_authorized(&victim_address, &victim_credential, state) + .is_authorized_for(&victim_address, &victim_credential, state) .unwrap()); }), }); @@ -1322,7 +1353,7 @@ fn test_remove_last_credential_orphans_address() { assert!(result.tx_receipt.is_successful()); let accounts = Accounts::::default(); assert!(!accounts - .is_authorized(&alice_address, &multisig_credential_id, state) + .is_authorized_for(&alice_address, &multisig_credential_id, state) .unwrap()); }), }); @@ -1345,7 +1376,7 @@ fn test_remove_last_credential_orphans_address() { TxEffect::Skipped(SkippedTxContents { error, .. }) => { let msg = error.to_string(); assert!( - msg.contains("not authorized for target address"), + msg.contains("not authorized for address override"), "expected resolver skip; got: {msg}" ); } @@ -1408,10 +1439,10 @@ fn test_multisig_key_rotation() { assert!(result.tx_receipt.is_successful()); let accounts = Accounts::::default(); assert!(accounts - .is_authorized(&target_address, &credential_id_1, state) + .is_authorized_for(&target_address, &credential_id_1, state) .unwrap()); assert!(accounts - .is_authorized(&target_address, &credential_id_2, state) + .is_authorized_for(&target_address, &credential_id_2, state) .unwrap()); }), }); @@ -1434,10 +1465,10 @@ fn test_multisig_key_rotation() { assert!(result.tx_receipt.is_successful()); let accounts = Accounts::::default(); assert!(!accounts - .is_authorized(&target_address, &credential_id_1, state) + .is_authorized_for(&target_address, &credential_id_1, state) .unwrap()); assert!(accounts - .is_authorized(&target_address, &credential_id_2, state) + .is_authorized_for(&target_address, &credential_id_2, state) .unwrap()); }), }); @@ -1480,7 +1511,7 @@ fn test_multisig_key_rotation() { TxEffect::Skipped(SkippedTxContents { error, .. }) => { let msg = error.to_string(); assert!( - msg.contains("not authorized for target address"), + msg.contains("not authorized for address override"), "expected resolver skip after rotation; got: {msg}" ); } @@ -1531,3 +1562,369 @@ fn test_feature_flag_gates_new_variants() { }), }); } + +/// `RotateCredentialOnAddress` atomically replaces an authorized credential +/// with a new one. The old tuple is deleted; the new tuple is written; a +/// separate unrelated authorization on the same address is untouched. +#[test] +fn test_rotate_credential_by_owner() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let old_credential = TestPrivateKey::generate().pub_key().credential_id(); + let new_credential = TestPrivateKey::generate().pub_key().credential_id(); + let unrelated_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: old_credential, + }, + )); + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: unrelated_credential, + }, + )); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: owner_address, + old_credential, + new_credential, + }, + ), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized_for(&owner_address, &old_credential, state) + .unwrap()); + assert!(accounts + .is_authorized_for(&owner_address, &new_credential, state) + .unwrap()); + assert!( + accounts + .is_authorized_for(&owner_address, &unrelated_credential, state) + .unwrap(), + "unrelated authorization must survive rotation" + ); + }), + }); +} + +/// Attempting to rotate out a credential that isn't authorized fails and +/// leaves state untouched. +#[test] +fn test_rotate_credential_old_not_authorized_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let ghost_credential = TestPrivateKey::generate().pub_key().credential_id(); + let new_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: owner_address, + old_credential: ghost_credential, + new_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId is not authorized for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + // The revert must leave the new tuple unwritten. + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized_for(&owner_address, &new_credential, state) + .unwrap()); + }), + }); +} + +/// Attempting to rotate in a credential that is already authorized fails +/// without revoking the old credential. +#[test] +fn test_rotate_credential_new_already_authorized_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let old_credential = TestPrivateKey::generate().pub_key().credential_id(); + let new_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: old_credential, + }, + )); + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: new_credential, + }, + )); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: owner_address, + old_credential, + new_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId already authorized for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + // The old credential must still be authorized — the revert rolls + // back the delete. + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized_for(&owner_address, &old_credential, state) + .unwrap()); + assert!(accounts + .is_authorized_for(&owner_address, &new_credential, state) + .unwrap()); + }), + }); +} + +/// A caller cannot rotate credentials on an address they don't own. +#[test] +fn test_rotate_credential_non_owner_rejected() { + let attacker = TestUser::::generate_with_default_balance(); + let victim = TestUser::::generate_with_default_balance(); + let victim_address = victim.address(); + let victim_credential = victim.credential_id(); + + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![attacker.clone(), victim]); + let mut genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + genesis.accounts.accounts.push(AccountData { + credential_id: victim_credential, + address: victim_address, + }); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + let attacker_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: attacker.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: victim_address, + old_credential: victim_credential, + new_credential: attacker_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Caller is not authorized to modify credentials for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized_for(&victim_address, &victim_credential, state) + .unwrap()); + assert!(!accounts + .is_authorized_for(&victim_address, &attacker_credential, state) + .unwrap()); + }), + }); +} + +/// `RotateCredentialOnAddress` is gated by the same feature flag as the +/// other write variants. +#[test] +fn test_rotate_credential_feature_flag_gated() { + let (user, mut runner) = setup_with_disable_custom_account_mappings(); + let user_address = user.address(); + let old_credential = TestPrivateKey::generate().pub_key().credential_id(); + let new_credential = TestPrivateKey::generate().pub_key().credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: user.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: user_address, + old_credential, + new_credential, + }, + ), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "Custom account mappings are disabled" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); +} + +/// Multisig rotation M1 → M2 in a single atomic `RotateCredentialOnAddress` +/// call, collapsing the two-step Add+Remove dance. +#[test] +fn test_multisig_key_rotation_atomic() { + use sov_modules_api::Multisig; + + let keys_1 = [ + TestPrivateKey::generate(), + TestPrivateKey::generate(), + TestPrivateKey::generate(), + ]; + let keys_2 = [ + TestPrivateKey::generate(), + TestPrivateKey::generate(), + TestPrivateKey::generate(), + ]; + let multisig_1 = Multisig::new(2, keys_1.iter().map(|k| k.pub_key()).collect()); + let multisig_2 = Multisig::new(2, keys_2.iter().map(|k| k.pub_key()).collect()); + let credential_id_1 = + multisig_1.credential_id::<<::CryptoSpec as CryptoSpec>::Hasher>(); + let credential_id_2 = + multisig_2.credential_id::<<::CryptoSpec as CryptoSpec>::Hasher>(); + + let multisig_user = + TestUser::generate_with_default_balance().add_credential_id(credential_id_1); + let target_address = multisig_user.address(); + + let genesis_config = + HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![multisig_user]); + let genesis = GenesisConfig::from_minimal_config(genesis_config.into()); + let mut runner: TestRunner = + TestRunner::new_with_genesis(genesis.into_genesis_params(), RT::default()); + + let mut rotate_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::RotateCredentialOnAddress { + address: target_address, + old_credential: credential_id_1, + new_credential: credential_id_2, + }, + Some(target_address), + 0, + ); + sign_v1(&mut rotate_tx, &keys_1[0]); + sign_v1(&mut rotate_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(rotate_tx), + assert: Box::new(move |result, state| { + assert!(result.tx_receipt.is_successful()); + let accounts = Accounts::::default(); + assert!(!accounts + .is_authorized_for(&target_address, &credential_id_1, state) + .unwrap()); + assert!(accounts + .is_authorized_for(&target_address, &credential_id_2, state) + .unwrap()); + }), + }); + + // M1 cannot bypass the revocation by omitting target_address and falling + // back to its canonical address. + let stale_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut m1_target_none_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::InsertCredentialId(stale_credential), + None, + 1, + ); + sign_v1(&mut m1_target_none_tx, &keys_1[0]); + sign_v1(&mut m1_target_none_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(m1_target_none_tx), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Skipped(SkippedTxContents { error, .. }) => { + let msg = error.to_string(); + assert!( + msg.contains("not authorized for resolved address"), + "expected resolver skip after target-less rotation fallback; got: {msg}" + ); + } + other => panic!("expected skipped tx after target-less fallback, got {other:?}"), + }), + }); + + // M2 now controls X. + let follow_up_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut m2_tx = make_v1_tx_with_call( + &multisig_2, + CallMessage::InsertCredentialId(follow_up_credential), + Some(target_address), + 0, + ); + sign_v1(&mut m2_tx, &keys_2[0]); + sign_v1(&mut m2_tx, &keys_2[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(m2_tx), + assert: Box::new(move |result, _state| { + assert!( + result.tx_receipt.is_successful(), + "M2 should now control X after atomic rotation; got {:?}", + result.tx_receipt + ); + }), + }); + + // M1 can no longer act as X. + let orphan_credential = TestPrivateKey::generate().pub_key().credential_id(); + let mut m1_tx = make_v1_tx_with_call( + &multisig_1, + CallMessage::InsertCredentialId(orphan_credential), + Some(target_address), + 1, + ); + sign_v1(&mut m1_tx, &keys_1[0]); + sign_v1(&mut m1_tx, &keys_1[1]); + runner.execute_transaction(TransactionTestCase { + input: submit_v1(m1_tx), + assert: Box::new(move |result, _state| match result.tx_receipt { + TxEffect::Skipped(SkippedTxContents { error, .. }) => { + let msg = error.to_string(); + assert!( + msg.contains("not authorized for address override"), + "expected resolver skip after rotation; got: {msg}" + ); + } + other => panic!("expected skipped tx after rotation, got {other:?}"), + }), + }); +} diff --git a/crates/module-system/module-schemas/schemas/sov-accounts.json b/crates/module-system/module-schemas/schemas/sov-accounts.json index e00152e75b..be48c8c5fb 100644 --- a/crates/module-system/module-schemas/schemas/sov-accounts.json +++ b/crates/module-system/module-schemas/schemas/sov-accounts.json @@ -17,7 +17,7 @@ "additionalProperties": false }, { - "description": "Authorizes `credential` to sign transactions that execute as `address`. The caller must currently be signing as `address` (i.e. `context.sender() == address`); this is naturally true after PR 2's resolver for V1 `target_address = Some(address)` and for V0/target=None where the signer's credential resolves into `address`. Fails if the tuple is already authorized.", + "description": "Authorizes `credential` to sign transactions that execute as `address`. The caller must currently be signing as `address`. Fails if the tuple is already authorized.", "type": "object", "required": [ "add_credential_to_address" @@ -85,6 +85,50 @@ } }, "additionalProperties": false + }, + { + "description": "Atomically swaps `old_credential` for `new_credential` on `address`. Functionally equivalent to a `RemoveCredentialFromAddress` followed by an `AddCredentialToAddress`, collapsed into a single call so the caller does not have to authorize two transactions during a rotation. The caller must currently be signing as `address`.", + "type": "object", + "required": [ + "rotate_credential_on_address" + ], + "properties": { + "rotate_credential_on_address": { + "type": "object", + "required": [ + "address", + "new_credential", + "old_credential" + ], + "properties": { + "address": { + "description": "The address whose credential set is being rotated. Must equal `context.sender()`.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "new_credential": { + "description": "The credential being authorized for `address`. Must not already be authorized.", + "allOf": [ + { + "$ref": "#/definitions/CredentialId" + } + ] + }, + "old_credential": { + "description": "The credential being revoked from `address`. Must be currently authorized.", + "allOf": [ + { + "$ref": "#/definitions/CredentialId" + } + ] + } + } + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/crates/module-system/sov-address/src/evm/address.rs b/crates/module-system/sov-address/src/evm/address.rs index 500aa32f1d..5a19743165 100644 --- a/crates/module-system/sov-address/src/evm/address.rs +++ b/crates/module-system/sov-address/src/evm/address.rs @@ -1,5 +1,5 @@ use crate::evm::public_key::EthereumPublicKey; -use crate::{MultiAddress, Not28Bytes}; +use crate::{MultiAddress, Not28Bytes, TryDecodeCredentialId}; use alloy_primitives::{Address, AddressError}; use borsh::{BorshDeserialize, BorshSerialize}; use k256::elliptic_curve::sec1::ToEncodedPoint; @@ -145,6 +145,21 @@ pub type MultiAddressEvm = MultiAddress; impl BasicAddress for EthereumAddress {} impl Not28Bytes for EthereumAddress {} +impl TryDecodeCredentialId for EthereumAddress { + fn try_decode_credential_id(credential_id: CredentialId) -> Option { + // EVM credentials pack a 20-byte address into the low 20 bytes of + // the 32-byte credential (see `EthereumAddress::as_credential_id`). + // Credentials derived from native public-key hashes have essentially + // random bytes in that leading 12-byte slot. + let bytes: &[u8] = credential_id.0.as_ref(); + if bytes[..12] == [0u8; 12] { + Some(Self::from(credential_id)) + } else { + None + } + } +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -152,10 +167,30 @@ mod tests { use borsh::{BorshDeserialize, BorshSerialize}; use sov_modules_api::configurable_spec::ConfigurableSpec; use sov_modules_api::execution_mode::Native; - use sov_modules_api::Spec; - use sov_test_utils::{MockDaSpec, MockZkvm}; use super::*; + + #[test] + fn credential_from_evm_address_roundtrips_to_vm_multi_address() { + let eth_addr = + EthereumAddress::from_str("0x71334bf1710D12c9f689cC819476fA589F08C64C").unwrap(); + let cred = eth_addr.as_credential_id(); + let back: MultiAddressEvm = cred.into(); + assert_eq!(back, MultiAddressEvm::Vm(eth_addr)); + } + + #[test] + fn credential_from_native_hash_stays_standard() { + let cred = CredentialId::from_bytes([0x42; 32]); + let back: MultiAddressEvm = cred.into(); + match back { + MultiAddressEvm::Standard(_) => {} + MultiAddressEvm::Vm(_) => panic!("non-zero-prefix credential must not decode to Vm"), + } + } + + use sov_modules_api::Spec; + use sov_test_utils::{MockDaSpec, MockZkvm}; type S = ConfigurableSpec; #[test] diff --git a/crates/module-system/sov-address/src/lib.rs b/crates/module-system/sov-address/src/lib.rs index 0dfc813124..5f57b60853 100644 --- a/crates/module-system/sov-address/src/lib.rs +++ b/crates/module-system/sov-address/src/lib.rs @@ -46,9 +46,12 @@ impl From for MultiAddress } } -impl From for MultiAddress { +impl From for MultiAddress { fn from(value: CredentialId) -> Self { - Self::Standard(Address::from(value)) + match VmAddress::try_decode_credential_id(value) { + Some(vm) => Self::Vm(vm), + None => Self::Standard(Address::from(value)), + } } } @@ -176,3 +179,14 @@ pub trait Not28Bytes {} pub trait FromVmAddress { fn from_vm_address(value: VmAddress) -> Self; } + +/// Attempts to reconstruct a VM-specific address from a [`CredentialId`]. +/// Credentials that originate from a VM address carry the address bytes in a +/// recoverable encoding (for example EVM credentials pack the 20-byte address +/// into the low 20 bytes of the 32-byte credential); implementors check for +/// that shape and decode back. Returning `None` means this credential does +/// not encode an address in this VM's format and falls back to the standard +/// hash-derived [`Address`]. +pub trait TryDecodeCredentialId: Sized { + fn try_decode_credential_id(credential_id: CredentialId) -> Option; +} diff --git a/crates/module-system/sov-capabilities/src/lib.rs b/crates/module-system/sov-capabilities/src/lib.rs index a7935f2c8c..8e55edfe74 100644 --- a/crates/module-system/sov-capabilities/src/lib.rs +++ b/crates/module-system/sov-capabilities/src/lib.rs @@ -314,6 +314,9 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' execution_context: ExecutionContext, sequencer_type: SequencerType, ) -> anyhow::Result> { + // `Some` requires an explicit `(addr, cred)` entry — overriding the default + // is opt-in. `None` allows the canonical-address fallback but still rejects + // when an explicit `false` (e.g. from `revoke_credential`) is recorded. let sender = match auth_data.address_override { Some(address_override) => { anyhow::ensure!( @@ -326,7 +329,17 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' ); address_override } - None => auth_data.default_address, + None => { + anyhow::ensure!( + self.accounts.is_authorized_for( + &auth_data.default_address, + &auth_data.credential_id, + state, + )?, + "not authorized for resolved address" + ); + auth_data.default_address + } }; Ok(Context::new( @@ -360,7 +373,17 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' ); address_override } - None => auth_data.default_address, + None => { + anyhow::ensure!( + self.accounts.is_authorized_for( + &auth_data.default_address, + &auth_data.credential_id, + state, + )?, + "not authorized for resolved address" + ); + auth_data.default_address + } }; Ok(Context::new( diff --git a/examples/demo-rollup/demo-rollup-schema.bin b/examples/demo-rollup/demo-rollup-schema.bin index 2598a10509c07d49888d12adf67f900990a6c529..98401ebd393fff618a2d00de4347153791cdcd0d 100644 GIT binary patch literal 13826 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 C=8C)k diff --git a/examples/demo-rollup/demo-rollup-schema.json b/examples/demo-rollup/demo-rollup-schema.json index 8956cc756a..69600db297 100644 --- a/examples/demo-rollup/demo-rollup-schema.json +++ b/examples/demo-rollup/demo-rollup-schema.json @@ -1,32 +1,23 @@ { "types": [ - { - "Struct": { - "type_name": "Transaction", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "versioned_tx", - "silent": false, - "value": { - "ByIndex": 1 - }, - "doc": "" - } - ] - } - }, { "Enum": { - "type_name": "VersionedTx", + "type_name": "Transaction", "variants": [ { "name": "V0", "discriminant": 0, "template": null, "value": { - "ByIndex": 2 + "ByIndex": 1 + } + }, + { + "name": "V1", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 172 } } ], @@ -40,7 +31,7 @@ "fields": [ { "value": { - "ByIndex": 3 + "ByIndex": 2 }, "silent": false, "doc": "" @@ -58,7 +49,12 @@ "display_name": "signature", "silent": false, "value": { - "ByIndex": 4 + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } }, "doc": "" }, @@ -66,7 +62,12 @@ "display_name": "pub_key", "silent": false, "value": { - "ByIndex": 5 + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } }, "doc": "" }, @@ -74,20 +75,15 @@ "display_name": "runtime_call", "silent": false, "value": { - "ByIndex": 6 + "ByIndex": 3 }, "doc": "" }, { - "display_name": "generation", + "display_name": "uniqueness", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 167 }, "doc": "" }, @@ -95,51 +91,7 @@ "display_name": "details", "silent": false, "value": { - "ByIndex": 113 - }, - "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "Ed25519Signature", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "msg_sig", - "silent": false, - "value": { - "Immediate": { - "ByteArray": { - "len": 64, - "display": "Hex" - } - } - }, - "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "Ed25519PublicKey", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "pub_key", - "silent": false, - "value": { - "Immediate": { - "ByteArray": { - "len": 32, - "display": "Hex" - } - } + "ByIndex": 170 }, "doc": "" } @@ -155,7 +107,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 7 + "ByIndex": 4 } }, { @@ -203,7 +155,7 @@ "discriminant": 6, "template": null, "value": { - "ByIndex": 51 + "ByIndex": 54 } }, { @@ -211,7 +163,7 @@ "discriminant": 7, "template": null, "value": { - "ByIndex": 53 + "ByIndex": 56 } }, { @@ -219,7 +171,7 @@ "discriminant": 8, "template": null, "value": { - "ByIndex": 54 + "ByIndex": 59 } }, { @@ -227,31 +179,71 @@ "discriminant": 9, "template": null, "value": { - "ByIndex": 55 + "ByIndex": 60 } }, { - "name": "Evm", + "name": "RevenueShare", "discriminant": 10, "template": null, "value": { - "ByIndex": 84 + "ByIndex": 89 } }, { - "name": "AccessPattern", + "name": "Mailbox", "discriminant": 11, "template": null, "value": { - "ByIndex": 87 + "ByIndex": 94 } }, { - "name": "SyntheticLoad", + "name": "InterchainGasPaymaster", "discriminant": 12, "template": null, "value": { - "ByIndex": 108 + "ByIndex": 103 + } + }, + { + "name": "MerkleTreeHook", + "discriminant": 13, + "template": null, + "value": { + "ByIndex": 113 + } + }, + { + "name": "Warp", + "discriminant": 14, + "template": null, + "value": { + "ByIndex": 114 + } + }, + { + "name": "Evm", + "discriminant": 15, + "template": null, + "value": { + "ByIndex": 134 + } + }, + { + "name": "AccessPattern", + "discriminant": 16, + "template": null, + "value": { + "ByIndex": 148 + } + }, + { + "name": "SyntheticLoad", + "discriminant": 17, + "template": null, + "value": { + "ByIndex": 162 } } ], @@ -265,7 +257,7 @@ "fields": [ { "value": { - "ByIndex": 8 + "ByIndex": 5 }, "silent": false, "doc": "" @@ -282,7 +274,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 9 + "ByIndex": 6 } }, { @@ -290,7 +282,7 @@ "discriminant": 1, "template": "Transfer to address {} {}.", "value": { - "ByIndex": 19 + "ByIndex": 18 } }, { @@ -298,7 +290,7 @@ "discriminant": 2, "template": null, "value": { - "ByIndex": 22 + "ByIndex": 21 } }, { @@ -306,7 +298,7 @@ "discriminant": 3, "template": null, "value": { - "ByIndex": 23 + "ByIndex": 22 } }, { @@ -314,7 +306,7 @@ "discriminant": 4, "template": null, "value": { - "ByIndex": 24 + "ByIndex": 23 } }, { @@ -322,7 +314,15 @@ "discriminant": 5, "template": null, "value": { - "ByIndex": 25 + "ByIndex": 24 + } + }, + { + "name": "TransferWithMemo", + "discriminant": 6, + "template": "Transfer to address {} {} with memo `{}`.", + "value": { + "ByIndex": 26 } } ], @@ -347,7 +347,7 @@ "display_name": "token_decimals", "silent": false, "value": { - "ByIndex": 10 + "ByIndex": 7 }, "doc": "" }, @@ -355,7 +355,7 @@ "display_name": "initial_balance", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" }, @@ -363,7 +363,7 @@ "display_name": "mint_to_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -371,7 +371,7 @@ "display_name": "admins", "silent": false, "value": { - "ByIndex": 17 + "ByIndex": 16 }, "doc": "" }, @@ -379,7 +379,7 @@ "display_name": "supply_cap", "silent": false, "value": { - "ByIndex": 18 + "ByIndex": 17 }, "doc": "" } @@ -420,22 +420,30 @@ }, { "Enum": { - "type_name": "MultiAddress", + "type_name": "MultiAddressEvmSolana", "variants": [ { "name": "Standard", "discriminant": 0, "template": null, "value": { - "ByIndex": 13 + "ByIndex": 10 } }, { - "name": "Vm", + "name": "Evm", "discriminant": 1, "template": null, "value": { - "ByIndex": 15 + "ByIndex": 12 + } + }, + { + "name": "Solana", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 14 } } ], @@ -449,7 +457,7 @@ "fields": [ { "value": { - "ByIndex": 14 + "ByIndex": 11 }, "silent": false, "doc": "" @@ -488,7 +496,7 @@ "fields": [ { "value": { - "ByIndex": 16 + "ByIndex": 13 }, "silent": false, "doc": "" @@ -516,17 +524,52 @@ ] } }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 15 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Base58" + } + } + }, + "silent": false, + "doc": "" + } + ] + } + }, { "Vec": { "value": { - "ByIndex": 12 + "ByIndex": 9 } } }, { "Option": { "value": { - "ByIndex": 11 + "ByIndex": 8 } } }, @@ -540,7 +583,7 @@ "display_name": "to", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -548,7 +591,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" } @@ -585,7 +628,7 @@ "display_name": "token_id", "silent": false, "value": { - "ByIndex": 21 + "ByIndex": 20 }, "doc": "" } @@ -626,7 +669,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" } @@ -643,7 +686,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" }, @@ -651,7 +694,7 @@ "display_name": "mint_to_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -668,7 +711,7 @@ "display_name": "token_id", "silent": false, "value": { - "ByIndex": 21 + "ByIndex": 20 }, "doc": "" } @@ -685,7 +728,7 @@ "display_name": "new_admin", "silent": false, "value": { - "ByIndex": 26 + "ByIndex": 25 }, "doc": "" }, @@ -693,7 +736,7 @@ "display_name": "token_id", "silent": false, "value": { - "ByIndex": 21 + "ByIndex": 20 }, "doc": "" } @@ -703,31 +746,64 @@ { "Option": { "value": { - "ByIndex": 12 + "ByIndex": 9 } } }, { - "Tuple": { - "template": null, + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_TransferWithMemo", + "template": "Transfer to address {} {} with memo `{}`.", "peekable": false, "fields": [ { + "display_name": "to", + "silent": false, "value": { - "ByIndex": 28 + "ByIndex": 9 }, - "silent": false, "doc": "" - } - ] - } - }, - { - "Enum": { - "type_name": "CallMessage", - "variants": [ + }, { - "name": "Register", + "display_name": "coins", + "silent": false, + "value": { + "ByIndex": 19 + }, + "doc": "" + }, + { + "display_name": "memo", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 28 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "Register", "discriminant": 0, "template": null, "value": { @@ -780,7 +856,7 @@ "display_name": "amount", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" } @@ -825,7 +901,7 @@ "display_name": "amount", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" } @@ -907,7 +983,7 @@ "display_name": "new_reward_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -986,7 +1062,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1001,7 +1077,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1016,7 +1092,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1076,7 +1152,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1091,7 +1167,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1125,6 +1201,30 @@ "value": { "ByIndex": 48 } + }, + { + "name": "AddCredentialToAddress", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 51 + } + }, + { + "name": "RemoveCredentialFromAddress", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 52 + } + }, + { + "name": "RotateCredentialOnAddress", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 53 + } } ], "hide_tag": false @@ -1180,6 +1280,89 @@ ] } }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_AddCredentialToAddress", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "credential", + "silent": false, + "value": { + "ByIndex": 49 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RemoveCredentialFromAddress", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "credential", + "silent": false, + "value": { + "ByIndex": 49 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RotateCredentialOnAddress", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "old_credential", + "silent": false, + "value": { + "ByIndex": 49 + }, + "doc": "" + }, + { + "display_name": "new_credential", + "silent": false, + "value": { + "ByIndex": 49 + }, + "doc": "" + } + ] + } + }, { "Tuple": { "template": null, @@ -1187,7 +1370,7 @@ "fields": [ { "value": { - "ByIndex": 52 + "ByIndex": 55 }, "silent": false, "doc": "" @@ -1209,7 +1392,7 @@ "fields": [ { "value": { - "ByIndex": 52 + "ByIndex": 57 }, "silent": false, "doc": "" @@ -1217,6 +1400,50 @@ ] } }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "TerminateSetupMode", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "SetOracleTime", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 58 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_SetOracleTime", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "milliseconds_since_epoch", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "i64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, { "Tuple": { "template": null, @@ -1224,7 +1451,7 @@ "fields": [ { "value": { - "ByIndex": 52 + "ByIndex": 55 }, "silent": false, "doc": "" @@ -1239,7 +1466,7 @@ "fields": [ { "value": { - "ByIndex": 56 + "ByIndex": 61 }, "silent": false, "doc": "" @@ -1256,7 +1483,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 57 + "ByIndex": 62 } }, { @@ -1264,7 +1491,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 73 + "ByIndex": 78 } }, { @@ -1272,7 +1499,7 @@ "discriminant": 2, "template": null, "value": { - "ByIndex": 74 + "ByIndex": 79 } } ], @@ -1289,7 +1516,7 @@ "display_name": "policy", "silent": false, "value": { - "ByIndex": 58 + "ByIndex": 63 }, "doc": "" } @@ -1306,7 +1533,7 @@ "display_name": "default_payee_policy", "silent": false, "value": { - "ByIndex": 59 + "ByIndex": 64 }, "doc": "" }, @@ -1314,7 +1541,7 @@ "display_name": "payees", "silent": false, "value": { - "ByIndex": 68 + "ByIndex": 73 }, "doc": "" }, @@ -1322,7 +1549,7 @@ "display_name": "authorized_updaters", "silent": false, "value": { - "ByIndex": 17 + "ByIndex": 16 }, "doc": "" }, @@ -1330,7 +1557,7 @@ "display_name": "authorized_sequencers", "silent": false, "value": { - "ByIndex": 70 + "ByIndex": 75 }, "doc": "" } @@ -1346,7 +1573,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 60 + "ByIndex": 65 } }, { @@ -1369,7 +1596,7 @@ "display_name": "max_fee", "silent": false, "value": { - "ByIndex": 18 + "ByIndex": 17 }, "doc": "" }, @@ -1377,7 +1604,7 @@ "display_name": "gas_limit", "silent": false, "value": { - "ByIndex": 61 + "ByIndex": 66 }, "doc": "" }, @@ -1385,7 +1612,7 @@ "display_name": "max_gas_price", "silent": false, "value": { - "ByIndex": 64 + "ByIndex": 69 }, "doc": "" }, @@ -1393,7 +1620,7 @@ "display_name": "transaction_limit", "silent": false, "value": { - "ByIndex": 67 + "ByIndex": 72 }, "doc": "" } @@ -1403,7 +1630,7 @@ { "Option": { "value": { - "ByIndex": 62 + "ByIndex": 67 } } }, @@ -1414,7 +1641,7 @@ "fields": [ { "value": { - "ByIndex": 63 + "ByIndex": 68 }, "silent": false, "doc": "" @@ -1438,7 +1665,7 @@ { "Option": { "value": { - "ByIndex": 65 + "ByIndex": 70 } } }, @@ -1452,7 +1679,7 @@ "display_name": "value", "silent": false, "value": { - "ByIndex": 66 + "ByIndex": 71 }, "doc": "" } @@ -1463,7 +1690,7 @@ "Array": { "len": 2, "value": { - "ByIndex": 11 + "ByIndex": 8 } } }, @@ -1482,7 +1709,7 @@ { "Vec": { "value": { - "ByIndex": 69 + "ByIndex": 74 } } }, @@ -1493,14 +1720,14 @@ "fields": [ { "value": { - "ByIndex": 12 + "ByIndex": 9 }, "silent": false, "doc": "" }, { "value": { - "ByIndex": 59 + "ByIndex": 64 }, "silent": false, "doc": "" @@ -1523,7 +1750,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 71 + "ByIndex": 76 } } ], @@ -1537,7 +1764,7 @@ "fields": [ { "value": { - "ByIndex": 72 + "ByIndex": 77 }, "silent": false, "doc": "" @@ -1562,7 +1789,7 @@ "display_name": "payer", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -1579,7 +1806,7 @@ "display_name": "payer", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -1587,7 +1814,7 @@ "display_name": "update", "silent": false, "value": { - "ByIndex": 75 + "ByIndex": 80 }, "doc": "" } @@ -1604,7 +1831,7 @@ "display_name": "sequencer_update", "silent": false, "value": { - "ByIndex": 76 + "ByIndex": 81 }, "doc": "" }, @@ -1612,7 +1839,7 @@ "display_name": "updaters_to_add", "silent": false, "value": { - "ByIndex": 81 + "ByIndex": 86 }, "doc": "" }, @@ -1620,7 +1847,7 @@ "display_name": "updaters_to_remove", "silent": false, "value": { - "ByIndex": 81 + "ByIndex": 86 }, "doc": "" }, @@ -1628,7 +1855,7 @@ "display_name": "payee_policies_to_set", "silent": false, "value": { - "ByIndex": 82 + "ByIndex": 87 }, "doc": "" }, @@ -1636,7 +1863,7 @@ "display_name": "payee_policies_to_delete", "silent": false, "value": { - "ByIndex": 81 + "ByIndex": 86 }, "doc": "" }, @@ -1644,7 +1871,7 @@ "display_name": "default_policy", "silent": false, "value": { - "ByIndex": 83 + "ByIndex": 88 }, "doc": "" } @@ -1654,7 +1881,7 @@ { "Option": { "value": { - "ByIndex": 77 + "ByIndex": 82 } } }, @@ -1673,7 +1900,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 78 + "ByIndex": 83 } } ], @@ -1687,7 +1914,7 @@ "fields": [ { "value": { - "ByIndex": 79 + "ByIndex": 84 }, "silent": false, "doc": "" @@ -1705,7 +1932,7 @@ "display_name": "to_add", "silent": false, "value": { - "ByIndex": 80 + "ByIndex": 85 }, "doc": "" }, @@ -1713,7 +1940,7 @@ "display_name": "to_remove", "silent": false, "value": { - "ByIndex": 80 + "ByIndex": 85 }, "doc": "" } @@ -1723,28 +1950,28 @@ { "Option": { "value": { - "ByIndex": 72 + "ByIndex": 77 } } }, { "Option": { "value": { - "ByIndex": 17 + "ByIndex": 16 } } }, { "Option": { "value": { - "ByIndex": 68 + "ByIndex": 73 } } }, { "Option": { "value": { - "ByIndex": 59 + "ByIndex": 64 } } }, @@ -1755,7 +1982,7 @@ "fields": [ { "value": { - "ByIndex": 85 + "ByIndex": 90 }, "silent": false, "doc": "" @@ -1764,16 +1991,65 @@ } }, { - "Struct": { + "Enum": { "type_name": "CallMessage", + "variants": [ + { + "name": "ActivateRevenueShare", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "DeactivateRevenueShare", + "discriminant": 1, + "template": null, + "value": null + }, + { + "name": "LowerRevenuePercentage", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 91 + } + }, + { + "name": "UpdateSovereignAdmin", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 92 + } + }, + { + "name": "WithdrawRewards", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 93 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_LowerRevenuePercentage", "template": null, "peekable": false, "fields": [ { - "display_name": "rlp", + "display_name": "percentage_in_basis_points", "silent": false, "value": { - "ByIndex": 86 + "Immediate": { + "Integer": [ + "u16", + "Decimal" + ] + } }, "doc": "" } @@ -1782,19 +2058,32 @@ }, { "Struct": { - "type_name": "RlpEvmTransaction", + "type_name": "__SovVirtualWallet_CallMessage_UpdateSovereignAdmin", "template": null, "peekable": false, "fields": [ { - "display_name": "rlp", + "display_name": "new_admin", "silent": false, "value": { - "Immediate": { - "ByteVec": { - "display": "Hex" - } - } + "ByIndex": 9 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_WithdrawRewards", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "token_id", + "silent": false, + "value": { + "ByIndex": 20 }, "doc": "" } @@ -1808,7 +2097,7 @@ "fields": [ { "value": { - "ByIndex": 88 + "ByIndex": 95 }, "silent": false, "doc": "" @@ -1818,115 +2107,31 @@ }, { "Enum": { - "type_name": "AccessPatternMessages", + "type_name": "CallMessage", "variants": [ { - "name": "WriteCells", + "name": "Dispatch", "discriminant": 0, "template": null, - "value": { - "ByIndex": 89 - } - }, - { - "name": "WriteCustom", - "discriminant": 1, - "template": null, - "value": { - "ByIndex": 90 - } - }, - { - "name": "ReadCells", - "discriminant": 2, - "template": null, - "value": { - "ByIndex": 92 - } - }, - { - "name": "HashBytes", - "discriminant": 3, - "template": null, - "value": { - "ByIndex": 93 - } - }, - { - "name": "HashCustom", - "discriminant": 4, - "template": null, - "value": { - "ByIndex": 94 - } - }, - { - "name": "StoreSignature", - "discriminant": 5, - "template": null, - "value": { - "ByIndex": 95 - } - }, - { - "name": "VerifySignature", - "discriminant": 6, - "template": null, - "value": null - }, - { - "name": "VerifyCustomSignature", - "discriminant": 7, - "template": null, "value": { "ByIndex": 96 } }, { - "name": "StoreSerializedString", - "discriminant": 8, - "template": null, - "value": { - "ByIndex": 97 - } - }, - { - "name": "DeserializeBytesAsString", - "discriminant": 9, - "template": null, - "value": null - }, - { - "name": "DeserializeCustomString", - "discriminant": 10, - "template": null, - "value": { - "ByIndex": 98 - } - }, - { - "name": "DeleteCells", - "discriminant": 11, + "name": "Process", + "discriminant": 1, "template": null, "value": { "ByIndex": 99 } }, { - "name": "SetHook", - "discriminant": 12, + "name": "Announce", + "discriminant": 2, "template": null, "value": { "ByIndex": 100 } - }, - { - "name": "UpdateAdmin", - "discriminant": 13, - "template": null, - "value": { - "ByIndex": 107 - } } ], "hide_tag": false @@ -1934,17 +2139,17 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCells", + "type_name": "__SovVirtualWallet_CallMessage_Dispatch", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -1952,28 +2157,42 @@ "doc": "" }, { - "display_name": "num_cells", + "display_name": "recipient", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 50 }, "doc": "" }, { - "display_name": "data_size", + "display_name": "body", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] - } + "ByIndex": 97 + }, + "doc": "" + }, + { + "display_name": "metadata", + "silent": false, + "value": { + "ByIndex": 98 + }, + "doc": "" + }, + { + "display_name": "relayer", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + }, + { + "display_name": "gas_payment_limit", + "silent": false, + "value": { + "ByIndex": 8 }, "doc": "" } @@ -1981,71 +2200,50 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCustom", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "begin", - "silent": false, "value": { "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] + "ByteVec": { + "display": "Hex" + } } }, - "doc": "" - }, - { - "display_name": "content", "silent": false, - "value": { - "ByIndex": 91 - }, "doc": "" } ] } }, { - "Vec": { + "Option": { "value": { - "Immediate": "String" + "ByIndex": 97 } } }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_ReadCells", + "type_name": "__SovVirtualWallet_CallMessage_Process", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "metadata", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 97 }, "doc": "" }, { - "display_name": "num_cells", + "display_name": "message", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 97 }, "doc": "" } @@ -2054,33 +2252,31 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_HashBytes", + "type_name": "__SovVirtualWallet_CallMessage_Announce", "template": null, "peekable": false, "fields": [ { - "display_name": "filler", + "display_name": "validator_address", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u8", - "Decimal" - ] - } + "ByIndex": 101 }, "doc": "" }, { - "display_name": "size", + "display_name": "storage_location", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] - } + "Immediate": "String" + }, + "doc": "" + }, + { + "display_name": "signature", + "silent": false, + "value": { + "ByIndex": 102 }, "doc": "" } @@ -2088,107 +2284,127 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_HashCustom", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "input", - "silent": false, "value": { "Immediate": { - "ByteVec": { + "ByteArray": { + "len": 20, "display": "Hex" } } }, + "silent": false, "doc": "" } ] } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSignature", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "sign", - "silent": false, "value": { - "ByIndex": 4 + "Immediate": { + "ByteArray": { + "len": 65, + "display": "Hex" + } + } }, + "silent": false, "doc": "" - }, + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ { - "display_name": "pub_key", - "silent": false, "value": { - "ByIndex": 5 + "ByIndex": 104 }, + "silent": false, "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "SetRelayerConfig", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 105 + } }, { - "display_name": "message", - "silent": false, + "name": "UpdateOracleData", + "discriminant": 1, + "template": null, "value": { - "Immediate": "String" - }, - "doc": "" + "ByIndex": 111 + } + }, + { + "name": "ClaimRewards", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 112 + } } - ] + ], + "hide_tag": false } }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_VerifyCustomSignature", + "type_name": "__SovVirtualWallet_CallMessage_SetRelayerConfig", "template": null, "peekable": false, "fields": [ { - "display_name": "sign", + "display_name": "domain_oracle_data", "silent": false, "value": { - "ByIndex": 4 + "ByIndex": 106 }, "doc": "" }, { - "display_name": "pub_key", + "display_name": "domain_default_gas", "silent": false, "value": { - "ByIndex": 5 + "ByIndex": 109 }, "doc": "" }, { - "display_name": "message", + "display_name": "default_gas", "silent": false, "value": { - "Immediate": "String" + "ByIndex": 8 }, "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSerializedString", - "template": null, - "peekable": false, - "fields": [ + }, { - "display_name": "input", + "display_name": "beneficiary", "silent": false, "value": { - "Immediate": { - "ByteVec": { - "display": "Hex" - } - } + "ByIndex": 25 }, "doc": "" } @@ -2196,39 +2412,25 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_DeserializeCustomString", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "input", - "silent": false, - "value": { - "Immediate": { - "ByteVec": { - "display": "Hex" - } - } - }, - "doc": "" - } - ] + "Vec": { + "value": { + "ByIndex": 107 + } } }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_DeleteCells", + "type_name": "DomainOracleData", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2236,15 +2438,10 @@ "doc": "" }, { - "display_name": "num_cells", + "display_name": "data_value", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 108 }, "doc": "" } @@ -2253,88 +2450,54 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_SetHook", + "type_name": "ExchangeRateAndGasPrice", "template": null, "peekable": false, "fields": [ { - "display_name": "pre", + "display_name": "gas_price", "silent": false, "value": { - "ByIndex": 101 + "ByIndex": 8 }, "doc": "" }, { - "display_name": "post", + "display_name": "token_exchange_rate", "silent": false, "value": { - "ByIndex": 101 + "Immediate": { + "Integer": [ + "u128", + "Decimal" + ] + } }, "doc": "" } ] } }, - { - "Option": { - "value": { - "ByIndex": 102 - } - } - }, { "Vec": { "value": { - "ByIndex": 103 + "ByIndex": 110 } } }, - { - "Enum": { - "type_name": "HooksConfig", - "variants": [ - { - "name": "Read", - "discriminant": 0, - "template": null, - "value": { - "ByIndex": 104 - } - }, - { - "name": "Write", - "discriminant": 1, - "template": null, - "value": { - "ByIndex": 105 - } - }, - { - "name": "Delete", - "discriminant": 2, - "template": null, - "value": { - "ByIndex": 106 - } - } - ], - "hide_tag": false - } - }, { "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Read", + "type_name": "DomainDefaultGas", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2342,15 +2505,10 @@ "doc": "" }, { - "display_name": "size", + "display_name": "default_gas", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 8 }, "doc": "" } @@ -2359,30 +2517,17 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Write", + "type_name": "__SovVirtualWallet_CallMessage_UpdateOracleData", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", - "silent": false, - "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } - }, - "doc": "" - }, - { - "display_name": "size", + "display_name": "domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2390,15 +2535,10 @@ "doc": "" }, { - "display_name": "data_size", + "display_name": "oracle_data", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] - } + "ByIndex": 108 }, "doc": "" } @@ -2407,33 +2547,15 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Delete", + "type_name": "__SovVirtualWallet_CallMessage_ClaimRewards", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", - "silent": false, - "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } - }, - "doc": "" - }, - { - "display_name": "size", + "display_name": "relayer_address", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 9 }, "doc": "" } @@ -2441,17 +2563,19 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "new_admin", - "silent": false, "value": { - "ByIndex": 12 + "Immediate": { + "Skip": { + "len": 0 + } + } }, + "silent": false, "doc": "" } ] @@ -2464,7 +2588,7 @@ "fields": [ { "value": { - "ByIndex": 109 + "ByIndex": 115 }, "silent": false, "doc": "" @@ -2477,27 +2601,43 @@ "type_name": "CallMessage", "variants": [ { - "name": "ReadAndSetManyIndividualValues", + "name": "Register", "discriminant": 0, "template": null, "value": { - "ByIndex": 110 + "ByIndex": 116 } }, { - "name": "ReadAndSetHeavyState", + "name": "Update", "discriminant": 1, "template": null, "value": { - "ByIndex": 111 + "ByIndex": 128 } }, { - "name": "RunCPUHeavyOperation", + "name": "EnrollRemoteRouter", "discriminant": 2, "template": null, "value": { - "ByIndex": 112 + "ByIndex": 131 + } + }, + { + "name": "UnEnrollRemoteRouter", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 132 + } + }, + { + "name": "TransferRemote", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 133 } } ], @@ -2506,65 +2646,165 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "type_name": "__SovVirtualWallet_CallMessage_Register", "template": null, "peekable": false, "fields": [ { - "display_name": "number_of_operations", + "display_name": "admin", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 117 }, "doc": "" }, { - "display_name": "salt", + "display_name": "token_source", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 119 + }, + "doc": "" + }, + { + "display_name": "ism", + "silent": false, + "value": { + "ByIndex": 122 + }, + "doc": "" + }, + { + "display_name": "remote_routers", + "silent": false, + "value": { + "ByIndex": 126 + }, + "doc": "" + }, + { + "display_name": "inbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "inbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "outbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "outbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "Admin", + "variants": [ + { + "name": "None", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "InsecureOwner", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 118 + } + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 9 }, + "silent": false, "doc": "" } ] } }, + { + "Enum": { + "type_name": "TokenKind", + "variants": [ + { + "name": "Synthetic", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 120 + } + }, + { + "name": "Collateral", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 121 + } + }, + { + "name": "Native", + "discriminant": 2, + "template": null, + "value": null + } + ], + "hide_tag": false + } + }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "type_name": "__SovVirtualWallet_TokenKind_Synthetic", "template": null, "peekable": false, "fields": [ { - "display_name": "number_of_new_values", + "display_name": "remote_token_id", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 50 }, "doc": "" }, { - "display_name": "max_heavy_state_size", + "display_name": "remote_decimals", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u8", "Decimal" ] } @@ -2572,15 +2812,10 @@ "doc": "" }, { - "display_name": "salt", + "display_name": "local_decimals", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 7 }, "doc": "" } @@ -2589,63 +2824,89 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "type_name": "__SovVirtualWallet_TokenKind_Collateral", "template": null, "peekable": false, "fields": [ { - "display_name": "iterations", + "display_name": "token", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 20 }, "doc": "" } ] } }, + { + "Enum": { + "type_name": "Ism", + "variants": [ + { + "name": "AlwaysTrust", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "TrustedRelayer", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 123 + } + }, + { + "name": "MessageIdMultisig", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 124 + } + } + ], + "hide_tag": false + } + }, { "Struct": { - "type_name": "TxDetails", + "type_name": "__SovVirtualWallet_Ism_TrustedRelayer", "template": null, "peekable": false, "fields": [ { - "display_name": "max_priority_fee_bips", - "silent": false, - "value": { - "ByIndex": 114 - }, - "doc": "" - }, - { - "display_name": "max_fee", + "display_name": "relayer", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 50 }, "doc": "" - }, + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_Ism_MessageIdMultisig", + "template": null, + "peekable": false, + "fields": [ { - "display_name": "gas_limit", + "display_name": "validators", "silent": false, "value": { - "ByIndex": 61 + "ByIndex": 125 }, "doc": "" }, { - "display_name": "chain_id", + "display_name": "threshold", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2655,6 +2916,20 @@ ] } }, + { + "Vec": { + "value": { + "ByIndex": 101 + } + } + }, + { + "Vec": { + "value": { + "ByIndex": 127 + } + } + }, { "Tuple": { "template": null, @@ -2664,134 +2939,1974 @@ "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } }, "silent": false, "doc": "" + }, + { + "value": { + "ByIndex": 50 + }, + "silent": false, + "doc": "" } ] } }, { "Struct": { - "type_name": "UnsignedTransaction", + "type_name": "__SovVirtualWallet_CallMessage_Update", "template": null, "peekable": false, "fields": [ { - "display_name": "runtime_call", + "display_name": "warp_route", "silent": false, "value": { - "ByIndex": 6 + "ByIndex": 50 }, "doc": "" }, { - "display_name": "generation", + "display_name": "admin", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 129 }, "doc": "" }, { - "display_name": "details", + "display_name": "ism", "silent": false, "value": { - "ByIndex": 113 + "ByIndex": 130 }, "doc": "" - } - ] - } - } - ], - "root_type_indices": [ - 0, - 115, - 6, - 12 - ], - "chain_data": { - "chain_id": 4321, - "chain_name": "TestChain" - }, - "templates": [ - {}, - {}, + }, + { + "display_name": "inbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "inbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "outbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "outbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + } + ] + } + }, { - "transfer": { - "preencoded_bytes": [ - 0, - 1 - ], - "inputs": [ - [ - "to", - { - "type_link": { - "ByIndex": 12 - }, - "offset": 2 - } - ], - [ - "amount", - { - "type_link": { - "Immediate": { - "Integer": [ - "u128", - { - "FixedPoint": { - "FromSiblingField": { - "field_index": 1, - "byte_offset": 31 - } - } - } - ] - } - }, - "offset": 2 + "Option": { + "value": { + "ByIndex": 117 + } + } + }, + { + "Option": { + "value": { + "ByIndex": 122 + } + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_EnrollRemoteRouter", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "warp_route", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "remote_domain", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "remote_router_address", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_UnEnrollRemoteRouter", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "warp_route", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "remote_domain", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_TransferRemote", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "warp_route", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "destination_domain", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "recipient", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "amount", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "relayer", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + }, + { + "display_name": "gas_payment_limit", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 135 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "Call", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 136 } - ], - [ - "token_id", - { - "type_link": { - "ByIndex": 21 - }, - "offset": 2 + }, + { + "name": "UpdateRuntimeConfig", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 138 } - ] + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 137 + }, + "silent": false, + "doc": "" + } ] } }, - {} - ], - "serde_metadata": [ { - "name": "Transaction", + "Struct": { + "type_name": "RlpEvmTransaction", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "rlp", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 139 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "EvmRuntimeConfigUpdate", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_hardfork", + "silent": false, + "value": { + "ByIndex": 140 + }, + "doc": "" + }, + { + "display_name": "new_contract_creation_policy", + "silent": false, + "value": { + "ByIndex": 142 + }, + "doc": "" + }, + { + "display_name": "chain_spec_update", + "silent": false, + "value": { + "ByIndex": 145 + }, + "doc": "" + }, + { + "display_name": "new_admin", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 141 + } + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + }, + { + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 143 + } + } + }, + { + "Enum": { + "type_name": "ContractCreationPolicyUpdate", + "variants": [ + { + "name": "Everyone", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "Allowlist", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 144 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_ContractCreationPolicyUpdate_Allowlist", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "add", + "silent": false, + "value": { + "ByIndex": 125 + }, + "doc": "" + }, + { + "display_name": "remove", + "silent": false, + "value": { + "ByIndex": 125 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 146 + } + } + }, + { + "Struct": { + "type_name": "ChainSpecUpdate", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_limit_contract_code_size", + "silent": false, + "value": { + "ByIndex": 147 + }, + "doc": "" + }, + { + "display_name": "new_block_gas_limit", + "silent": false, + "value": { + "ByIndex": 72 + }, + "doc": "" + }, + { + "display_name": "new_tx_gas_limit", + "silent": false, + "value": { + "ByIndex": 72 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + } + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 149 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "AccessPatternMessages", + "variants": [ + { + "name": "WriteCells", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 150 + } + }, + { + "name": "WriteCustom", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 151 + } + }, + { + "name": "ReadCells", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 153 + } + }, + { + "name": "HashBytes", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 154 + } + }, + { + "name": "HashCustom", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 155 + } + }, + { + "name": "StoreSignature", + "discriminant": 5, + "template": null, + "value": { + "ByIndex": 156 + } + }, + { + "name": "VerifySignature", + "discriminant": 6, + "template": null, + "value": null + }, + { + "name": "VerifyCustomSignature", + "discriminant": 7, + "template": null, + "value": { + "ByIndex": 157 + } + }, + { + "name": "StoreSerializedString", + "discriminant": 8, + "template": null, + "value": { + "ByIndex": 158 + } + }, + { + "name": "DeserializeBytesAsString", + "discriminant": 9, + "template": null, + "value": null + }, + { + "name": "DeserializeCustomString", + "discriminant": 10, + "template": null, + "value": { + "ByIndex": 159 + } + }, + { + "name": "DeleteCells", + "discriminant": 11, + "template": null, + "value": { + "ByIndex": 160 + } + }, + { + "name": "UpdateAdmin", + "discriminant": 12, + "template": null, + "value": { + "ByIndex": 161 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "data_size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCustom", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "content", + "silent": false, + "value": { + "ByIndex": 152 + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "Immediate": "String" + } + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_ReadCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_HashBytes", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "filler", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_HashCustom", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "sign", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "message", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_VerifyCustomSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "sign", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "message", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSerializedString", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_DeserializeCustomString", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_DeleteCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_admin", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 163 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "ReadAndSetManyIndividualValues", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 164 + } + }, + { + "name": "ReadAndSetHeavyState", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 165 + } + }, + { + "name": "RunCPUHeavyOperation", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 166 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "number_of_operations", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "salt", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "number_of_new_values", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "max_heavy_state_size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "salt", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "iterations", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "UniquenessData", + "variants": [ + { + "name": "Nonce", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 168 + } + }, + { + "name": "Generation", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 169 + } + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "TxDetails", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "max_priority_fee_bips", + "silent": false, + "value": { + "ByIndex": 171 + }, + "doc": "" + }, + { + "display_name": "max_fee", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "gas_limit", + "silent": false, + "value": { + "ByIndex": 66 + }, + "doc": "" + }, + { + "display_name": "chain_id", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 173 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "Version1", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "signatures", + "silent": false, + "value": { + "ByIndex": 174 + }, + "doc": "" + }, + { + "display_name": "unused_pub_keys", + "silent": false, + "value": { + "ByIndex": 176 + }, + "doc": "" + }, + { + "display_name": "min_signers", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + }, + { + "display_name": "target_address", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "ByIndex": 175 + } + } + }, + { + "Struct": { + "type_name": "PubKeyAndSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "signature", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + } + } + }, + { + "Enum": { + "type_name": "UnsignedTransaction", + "variants": [ + { + "name": "V0", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 178 + } + }, + { + "name": "V1", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 180 + } + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 179 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "UnsignedTransactionV0", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 181 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "UnsignedTransactionV1", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + }, + { + "display_name": "credential_address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "target_address", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + } + ], + "root_type_indices": [ + 0, + 177, + 3, + 9 + ], + "chain_data": { + "chain_id": 4321, + "chain_name": "TestChain" + }, + "templates": [ + {}, + {}, + { + "transfer": { + "preencoded_bytes": [ + 0, + 1 + ], + "inputs": [ + [ + "to", + { + "type_link": { + "ByIndex": 9 + }, + "offset": 2 + } + ], + [ + "amount", + { + "type_link": { + "Immediate": { + "Integer": [ + "u128", + { + "FixedPoint": { + "FromSiblingField": { + "field_index": 1, + "byte_offset": 31 + } + } + } + ] + } + }, + "offset": 2 + } + ], + [ + "token_id", + { + "type_link": { + "ByIndex": 20 + }, + "offset": 2 + } + ] + ] + } + }, + {} + ], + "serde_metadata": [ + { + "name": "Transaction", + "fields_or_variants": [ + { + "name": "V0" + }, + { + "name": "V1" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "Version0", + "fields_or_variants": [ + { + "name": "signature" + }, + { + "name": "pub_key" + }, + { + "name": "runtime_call" + }, + { + "name": "uniqueness" + }, + { + "name": "details" + } + ] + }, + { + "name": "RuntimeCall", + "fields_or_variants": [ + { + "name": "bank" + }, + { + "name": "sequencer_registry" + }, + { + "name": "operator_incentives" + }, + { + "name": "attester_incentives" + }, + { + "name": "prover_incentives" + }, + { + "name": "accounts" + }, + { + "name": "uniqueness" + }, + { + "name": "chain_state" + }, + { + "name": "blob_storage" + }, + { + "name": "paymaster" + }, + { + "name": "revenue_share" + }, + { + "name": "mailbox" + }, + { + "name": "interchain_gas_paymaster" + }, + { + "name": "merkle_tree_hook" + }, + { + "name": "warp" + }, + { + "name": "evm" + }, + { + "name": "access_pattern" + }, + { + "name": "synthetic_load" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "create_token" + }, + { + "name": "transfer" + }, + { + "name": "burn" + }, + { + "name": "mint" + }, + { + "name": "freeze" + }, + { + "name": "update_admin" + }, + { + "name": "transfer_with_memo" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_CreateToken", + "fields_or_variants": [ + { + "name": "token_name" + }, + { + "name": "token_decimals" + }, + { + "name": "initial_balance" + }, + { + "name": "mint_to_address" + }, + { + "name": "admins" + }, + { + "name": "supply_cap" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "MultiAddressEvmSolana", + "fields_or_variants": [ + { + "name": "Standard" + }, + { + "name": "Evm" + }, + { + "name": "Solana" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Transfer", + "fields_or_variants": [ + { + "name": "to" + }, + { + "name": "coins" + } + ] + }, + { + "name": "Coins", + "fields_or_variants": [ + { + "name": "amount" + }, + { + "name": "token_id" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Burn", + "fields_or_variants": [ + { + "name": "coins" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Mint", + "fields_or_variants": [ + { + "name": "coins" + }, + { + "name": "mint_to_address" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Freeze", + "fields_or_variants": [ + { + "name": "token_id" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateAdmin", + "fields_or_variants": [ + { + "name": "new_admin" + }, + { + "name": "token_id" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_TransferWithMemo", + "fields_or_variants": [ + { + "name": "to" + }, + { + "name": "coins" + }, + { + "name": "memo" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "register" + }, + { + "name": "deposit" + }, + { + "name": "initiate_withdrawal" + }, + { + "name": "withdraw" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Register", + "fields_or_variants": [ + { + "name": "da_address" + }, + { + "name": "amount" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Deposit", + "fields_or_variants": [ + { + "name": "da_address" + }, + { + "name": "amount" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_InitiateWithdrawal", + "fields_or_variants": [ + { + "name": "da_address" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Withdraw", "fields_or_variants": [ { - "name": "versioned_tx" + "name": "da_address" } ] }, { - "name": "VersionedTx", + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", "fields_or_variants": [ { - "name": "V0" + "name": "update_reward_address" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateRewardAddress", + "fields_or_variants": [ + { + "name": "new_reward_address" } ] }, @@ -2800,82 +4915,289 @@ "fields_or_variants": [] }, { - "name": "Version0", + "name": "CallMessage", "fields_or_variants": [ { - "name": "signature" + "name": "register_attester" }, { - "name": "pub_key" + "name": "begin_exit_attester" }, { - "name": "runtime_call" + "name": "exit_attester" }, { - "name": "generation" + "name": "register_challenger" }, { - "name": "details" + "name": "exit_challenger" + }, + { + "name": "deposit_attester" } ] }, { - "name": "Ed25519Signature", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", "fields_or_variants": [ { - "name": "msg_sig" + "name": "register" + }, + { + "name": "deposit" + }, + { + "name": "exit" } ] }, { - "name": "Ed25519PublicKey", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", "fields_or_variants": [ { - "name": "pub_key" + "name": "insert_credential_id" + }, + { + "name": "add_credential_to_address" + }, + { + "name": "remove_credential_from_address" + }, + { + "name": "rotate_credential_on_address" } ] }, { - "name": "RuntimeCall", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_AddCredentialToAddress", "fields_or_variants": [ { - "name": "bank" + "name": "address" }, { - "name": "sequencer_registry" + "name": "credential" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RemoveCredentialFromAddress", + "fields_or_variants": [ + { + "name": "address" }, { - "name": "operator_incentives" + "name": "credential" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RotateCredentialOnAddress", + "fields_or_variants": [ + { + "name": "address" }, { - "name": "attester_incentives" + "name": "old_credential" }, { - "name": "prover_incentives" + "name": "new_credential" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "NotInstantiable", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "TerminateSetupMode" }, { - "name": "accounts" + "name": "SetOracleTime" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_SetOracleTime", + "fields_or_variants": [ + { + "name": "milliseconds_since_epoch" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "register_paymaster" }, { - "name": "uniqueness" + "name": "set_payer_for_sequencer" }, { - "name": "chain_state" + "name": "update_policy" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", + "fields_or_variants": [ + { + "name": "policy" + } + ] + }, + { + "name": "PaymasterPolicyInitializer", + "fields_or_variants": [ + { + "name": "default_payee_policy" }, { - "name": "blob_storage" + "name": "payees" }, { - "name": "paymaster" + "name": "authorized_updaters" }, { - "name": "evm" + "name": "authorized_sequencers" + } + ] + }, + { + "name": "PayeePolicy", + "fields_or_variants": [ + { + "name": "allow" }, { - "name": "access_pattern" + "name": "deny" + } + ] + }, + { + "name": "__SovVirtualWallet_PayeePolicy_Allow", + "fields_or_variants": [ + { + "name": "max_fee" + }, + { + "name": "gas_limit" + }, + { + "name": "max_gas_price" + }, + { + "name": "transaction_limit" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "GasPrice", + "fields_or_variants": [ + { + "name": "value" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "AuthorizedSequencers", + "fields_or_variants": [ + { + "name": "all" }, { - "name": "synthetic_load" + "name": "some" } ] }, @@ -2884,48 +5206,48 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_SetPayerForSequencer", "fields_or_variants": [ { - "name": "create_token" - }, - { - "name": "transfer" - }, - { - "name": "burn" - }, - { - "name": "mint" - }, + "name": "payer" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdatePolicy", + "fields_or_variants": [ { - "name": "freeze" + "name": "payer" }, { - "name": "update_admin" + "name": "update" } ] }, { - "name": "__SovVirtualWallet_CallMessage_CreateToken", + "name": "PolicyUpdate", "fields_or_variants": [ { - "name": "token_name" + "name": "sequencer_update" }, { - "name": "token_decimals" + "name": "updaters_to_add" }, { - "name": "initial_balance" + "name": "updaters_to_remove" }, { - "name": "mint_to_address" + "name": "payee_policies_to_set" }, { - "name": "admins" + "name": "payee_policies_to_delete" }, { - "name": "supply_cap" + "name": "default_policy" } ] }, @@ -2934,17 +5256,13 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "MultiAddress", + "name": "SequencerSetUpdate", "fields_or_variants": [ { - "name": "Standard" + "name": "allow_all" }, { - "name": "Vm" + "name": "update" } ] }, @@ -2952,6 +5270,17 @@ "name": "", "fields_or_variants": [] }, + { + "name": "AllowedSequencerUpdate", + "fields_or_variants": [ + { + "name": "to_add" + }, + { + "name": "to_remove" + } + ] + }, { "name": "", "fields_or_variants": [] @@ -2973,22 +5302,44 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_Transfer", + "name": "CallMessage", "fields_or_variants": [ { - "name": "to" + "name": "activate_revenue_share" }, { - "name": "coins" + "name": "deactivate_revenue_share" + }, + { + "name": "lower_revenue_percentage" + }, + { + "name": "update_sovereign_admin" + }, + { + "name": "withdraw_rewards" } ] }, { - "name": "Coins", + "name": "__SovVirtualWallet_CallMessage_LowerRevenuePercentage", "fields_or_variants": [ { - "name": "amount" - }, + "name": "percentage_in_basis_points" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateSovereignAdmin", + "fields_or_variants": [ + { + "name": "new_admin" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_WithdrawRewards", + "fields_or_variants": [ { "name": "token_id" } @@ -2999,40 +5350,72 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_Burn", + "name": "CallMessage", "fields_or_variants": [ { - "name": "coins" + "name": "dispatch" + }, + { + "name": "process" + }, + { + "name": "announce" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Mint", + "name": "__SovVirtualWallet_CallMessage_Dispatch", "fields_or_variants": [ { - "name": "coins" + "name": "domain" }, { - "name": "mint_to_address" + "name": "recipient" + }, + { + "name": "body" + }, + { + "name": "metadata" + }, + { + "name": "relayer" + }, + { + "name": "gas_payment_limit" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Freeze", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Process", "fields_or_variants": [ { - "name": "token_id" + "name": "metadata" + }, + { + "name": "message" } ] }, { - "name": "__SovVirtualWallet_CallMessage_UpdateAdmin", + "name": "__SovVirtualWallet_CallMessage_Announce", "fields_or_variants": [ { - "name": "new_admin" + "name": "validator_address" }, { - "name": "token_id" + "name": "storage_location" + }, + { + "name": "signature" } ] }, @@ -3044,31 +5427,38 @@ "name": "", "fields_or_variants": [] }, + { + "name": "", + "fields_or_variants": [] + }, { "name": "CallMessage", "fields_or_variants": [ { - "name": "register" - }, - { - "name": "deposit" + "name": "set_relayer_config" }, { - "name": "initiate_withdrawal" + "name": "update_oracle_data" }, { - "name": "withdraw" + "name": "claim_rewards" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Register", + "name": "__SovVirtualWallet_CallMessage_SetRelayerConfig", "fields_or_variants": [ { - "name": "da_address" + "name": "domain_oracle_data" }, { - "name": "amount" + "name": "domain_default_gas" + }, + { + "name": "default_gas" + }, + { + "name": "beneficiary" } ] }, @@ -3077,49 +5467,58 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_Deposit", + "name": "DomainOracleData", "fields_or_variants": [ { - "name": "da_address" + "name": "domain" }, { - "name": "amount" + "name": "data_value" } ] }, { - "name": "__SovVirtualWallet_CallMessage_InitiateWithdrawal", + "name": "ExchangeRateAndGasPrice", "fields_or_variants": [ { - "name": "da_address" + "name": "gas_price" + }, + { + "name": "token_exchange_rate" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Withdraw", + "name": "", + "fields_or_variants": [] + }, + { + "name": "DomainDefaultGas", "fields_or_variants": [ { - "name": "da_address" + "name": "domain" + }, + { + "name": "default_gas" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "CallMessage", + "name": "__SovVirtualWallet_CallMessage_UpdateOracleData", "fields_or_variants": [ { - "name": "update_reward_address" + "name": "domain" + }, + { + "name": "oracle_data" } ] }, { - "name": "__SovVirtualWallet_CallMessage_UpdateRewardAddress", + "name": "__SovVirtualWallet_CallMessage_ClaimRewards", "fields_or_variants": [ { - "name": "new_reward_address" + "name": "relayer_address" } ] }, @@ -3127,76 +5526,67 @@ "name": "", "fields_or_variants": [] }, + { + "name": "", + "fields_or_variants": [] + }, { "name": "CallMessage", "fields_or_variants": [ { - "name": "register_attester" - }, - { - "name": "begin_exit_attester" + "name": "register" }, { - "name": "exit_attester" + "name": "update" }, { - "name": "register_challenger" + "name": "enroll_remote_router" }, { - "name": "exit_challenger" + "name": "un_enroll_remote_router" }, { - "name": "deposit_attester" + "name": "transfer_remote" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "CallMessage", + "name": "__SovVirtualWallet_CallMessage_Register", "fields_or_variants": [ { - "name": "register" + "name": "admin" }, { - "name": "deposit" + "name": "token_source" }, { - "name": "exit" + "name": "ism" + }, + { + "name": "remote_routers" + }, + { + "name": "inbound_transferrable_tokens_limit" + }, + { + "name": "inbound_limit_replenishment_per_slot" + }, + { + "name": "outbound_transferrable_tokens_limit" + }, + { + "name": "outbound_limit_replenishment_per_slot" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "CallMessage", + "name": "Admin", "fields_or_variants": [ { - "name": "insert_credential_id" + "name": "None" + }, + { + "name": "InsecureOwner" } ] }, @@ -3205,127 +5595,73 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "NotInstantiable", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "CallMessage", + "name": "TokenKind", "fields_or_variants": [ { - "name": "register_paymaster" + "name": "Synthetic" }, { - "name": "set_payer_for_sequencer" + "name": "Collateral" }, { - "name": "update_policy" - } - ] - }, - { - "name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", - "fields_or_variants": [ - { - "name": "policy" + "name": "Native" } ] }, { - "name": "PaymasterPolicyInitializer", + "name": "__SovVirtualWallet_TokenKind_Synthetic", "fields_or_variants": [ { - "name": "default_payee_policy" - }, - { - "name": "payees" + "name": "remote_token_id" }, { - "name": "authorized_updaters" + "name": "remote_decimals" }, { - "name": "authorized_sequencers" + "name": "local_decimals" } ] }, { - "name": "PayeePolicy", + "name": "__SovVirtualWallet_TokenKind_Collateral", "fields_or_variants": [ { - "name": "allow" - }, - { - "name": "deny" + "name": "token" } ] }, { - "name": "__SovVirtualWallet_PayeePolicy_Allow", + "name": "Ism", "fields_or_variants": [ { - "name": "max_fee" - }, - { - "name": "gas_limit" + "name": "AlwaysTrust" }, { - "name": "max_gas_price" + "name": "TrustedRelayer" }, { - "name": "transaction_limit" + "name": "MessageIdMultisig" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "GasPrice", + "name": "__SovVirtualWallet_Ism_TrustedRelayer", "fields_or_variants": [ { - "name": "value" + "name": "relayer" } ] }, { - "name": "", - "fields_or_variants": [] + "name": "__SovVirtualWallet_Ism_MessageIdMultisig", + "fields_or_variants": [ + { + "name": "validators" + }, + { + "name": "threshold" + } + ] }, { "name": "", @@ -3340,13 +5676,28 @@ "fields_or_variants": [] }, { - "name": "AuthorizedSequencers", + "name": "__SovVirtualWallet_CallMessage_Update", "fields_or_variants": [ { - "name": "all" + "name": "warp_route" }, { - "name": "some" + "name": "admin" + }, + { + "name": "ism" + }, + { + "name": "inbound_transferrable_tokens_limit" + }, + { + "name": "inbound_limit_replenishment_per_slot" + }, + { + "name": "outbound_transferrable_tokens_limit" + }, + { + "name": "outbound_limit_replenishment_per_slot" } ] }, @@ -3359,44 +5710,50 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_SetPayerForSequencer", + "name": "__SovVirtualWallet_CallMessage_EnrollRemoteRouter", "fields_or_variants": [ { - "name": "payer" + "name": "warp_route" + }, + { + "name": "remote_domain" + }, + { + "name": "remote_router_address" } ] }, { - "name": "__SovVirtualWallet_CallMessage_UpdatePolicy", + "name": "__SovVirtualWallet_CallMessage_UnEnrollRemoteRouter", "fields_or_variants": [ { - "name": "payer" + "name": "warp_route" }, { - "name": "update" + "name": "remote_domain" } ] }, { - "name": "PolicyUpdate", + "name": "__SovVirtualWallet_CallMessage_TransferRemote", "fields_or_variants": [ { - "name": "sequencer_update" + "name": "warp_route" }, { - "name": "updaters_to_add" + "name": "destination_domain" }, { - "name": "updaters_to_remove" + "name": "recipient" }, { - "name": "payee_policies_to_set" + "name": "amount" }, { - "name": "payee_policies_to_delete" + "name": "relayer" }, { - "name": "default_policy" + "name": "gas_payment_limit" } ] }, @@ -3405,13 +5762,13 @@ "fields_or_variants": [] }, { - "name": "SequencerSetUpdate", + "name": "CallMessage", "fields_or_variants": [ { - "name": "allow_all" + "name": "Call" }, { - "name": "update" + "name": "UpdateRuntimeConfig" } ] }, @@ -3420,13 +5777,10 @@ "fields_or_variants": [] }, { - "name": "AllowedSequencerUpdate", + "name": "RlpEvmTransaction", "fields_or_variants": [ { - "name": "to_add" - }, - { - "name": "to_remove" + "name": "rlp" } ] }, @@ -3435,8 +5789,21 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] + "name": "EvmRuntimeConfigUpdate", + "fields_or_variants": [ + { + "name": "new_hardfork" + }, + { + "name": "new_contract_creation_policy" + }, + { + "name": "chain_spec_update" + }, + { + "name": "new_admin" + } + ] }, { "name": "", @@ -3451,18 +5818,42 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "ContractCreationPolicyUpdate", "fields_or_variants": [ { - "name": "rlp" + "name": "Everyone" + }, + { + "name": "Allowlist" } ] }, { - "name": "RlpEvmTransaction", + "name": "__SovVirtualWallet_ContractCreationPolicyUpdate_Allowlist", "fields_or_variants": [ { - "name": "rlp" + "name": "add" + }, + { + "name": "remove" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "ChainSpecUpdate", + "fields_or_variants": [ + { + "name": "new_limit_contract_code_size" + }, + { + "name": "new_block_gas_limit" + }, + { + "name": "new_tx_gas_limit" } ] }, @@ -3470,6 +5861,10 @@ "name": "", "fields_or_variants": [] }, + { + "name": "", + "fields_or_variants": [] + }, { "name": "AccessPatternMessages", "fields_or_variants": [ @@ -3509,9 +5904,6 @@ { "name": "delete_cells" }, - { - "name": "set_hook" - }, { "name": "update_admin" } @@ -3632,13 +6024,10 @@ ] }, { - "name": "__SovVirtualWallet_AccessPatternMessages_SetHook", + "name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", "fields_or_variants": [ { - "name": "pre" - }, - { - "name": "post" + "name": "new_admin" } ] }, @@ -3647,64 +6036,60 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "HooksConfig", + "name": "CallMessage", "fields_or_variants": [ { - "name": "Read" + "name": "read_and_set_many_individual_values" }, { - "name": "Write" + "name": "read_and_set_heavy_state" }, { - "name": "Delete" + "name": "run_cpu_heavy_operation" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Read", + "name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", "fields_or_variants": [ { - "name": "begin" + "name": "number_of_operations" }, { - "name": "size" + "name": "salt" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Write", + "name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", "fields_or_variants": [ { - "name": "begin" + "name": "number_of_new_values" }, { - "name": "size" + "name": "max_heavy_state_size" }, { - "name": "data_size" + "name": "salt" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Delete", + "name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", "fields_or_variants": [ { - "name": "begin" - }, - { - "name": "size" + "name": "iterations" } ] }, { - "name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", + "name": "UniquenessData", "fields_or_variants": [ { - "name": "new_admin" + "name": "nonce" + }, + { + "name": "generation" } ] }, @@ -3713,66 +6098,105 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "", + "fields_or_variants": [] + }, + { + "name": "TxDetails", "fields_or_variants": [ { - "name": "read_and_set_many_individual_values" + "name": "max_priority_fee_bips" }, { - "name": "read_and_set_heavy_state" + "name": "max_fee" }, { - "name": "run_cpu_heavy_operation" + "name": "gas_limit" + }, + { + "name": "chain_id" } ] }, { - "name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "Version1", "fields_or_variants": [ { - "name": "number_of_operations" + "name": "signatures" }, { - "name": "salt" + "name": "unused_pub_keys" + }, + { + "name": "min_signers" + }, + { + "name": "runtime_call" + }, + { + "name": "uniqueness" + }, + { + "name": "details" + }, + { + "name": "target_address" } ] }, { - "name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "name": "", + "fields_or_variants": [] + }, + { + "name": "PubKeyAndSignature", "fields_or_variants": [ { - "name": "number_of_new_values" - }, - { - "name": "max_heavy_state_size" + "name": "signature" }, { - "name": "salt" + "name": "pub_key" } ] }, { - "name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "name": "", + "fields_or_variants": [] + }, + { + "name": "UnsignedTransaction", "fields_or_variants": [ { - "name": "iterations" + "name": "V0" + }, + { + "name": "V1" } ] }, { - "name": "TxDetails", + "name": "", + "fields_or_variants": [] + }, + { + "name": "UnsignedTransactionV0", "fields_or_variants": [ { - "name": "max_priority_fee_bips" - }, - { - "name": "max_fee" + "name": "runtime_call" }, { - "name": "gas_limit" + "name": "uniqueness" }, { - "name": "chain_id" + "name": "details" } ] }, @@ -3781,16 +6205,22 @@ "fields_or_variants": [] }, { - "name": "UnsignedTransaction", + "name": "UnsignedTransactionV1", "fields_or_variants": [ { "name": "runtime_call" }, { - "name": "generation" + "name": "uniqueness" }, { "name": "details" + }, + { + "name": "credential_address" + }, + { + "name": "target_address" } ] } diff --git a/examples/demo-rollup/stf/stf-declaration/src/address.rs b/examples/demo-rollup/stf/stf-declaration/src/address.rs index 3b73b03474..28116de86c 100644 --- a/examples/demo-rollup/stf/stf-declaration/src/address.rs +++ b/examples/demo-rollup/stf/stf-declaration/src/address.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sov_address::{EthereumAddress, FromVmAddress}; +use sov_address::{EthereumAddress, FromVmAddress, TryDecodeCredentialId}; use sov_hyperlane_integration::HyperlaneAddress; use sov_modules_api::macros::UniversalWallet; use sov_modules_api::{ @@ -54,6 +54,14 @@ impl From for MultiAddressEvmSolana { impl From for MultiAddressEvmSolana { fn from(value: CredentialId) -> Self { + // EVM credentials pack the 20-byte address into the low 20 bytes of + // the 32-byte credential (12 zero bytes of padding); recover the + // `Evm` variant so the canonical-address relationship round-trips + // for EVM signers. Native and Solana credentials are hashes with + // no such structural marker, so they fall back to `Standard`. + if let Some(eth_addr) = EthereumAddress::try_decode_credential_id(value) { + return Self::Evm(eth_addr); + } Self::Standard(Address::from(value)) } } diff --git a/typescript/packages/__fixtures__/demo-rollup-schema.json b/typescript/packages/__fixtures__/demo-rollup-schema.json index 81efd8936e..69600db297 100644 --- a/typescript/packages/__fixtures__/demo-rollup-schema.json +++ b/typescript/packages/__fixtures__/demo-rollup-schema.json @@ -1,32 +1,23 @@ { "types": [ - { - "Struct": { - "type_name": "Transaction", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "versioned_tx", - "silent": false, - "value": { - "ByIndex": 1 - }, - "doc": "" - } - ] - } - }, { "Enum": { - "type_name": "VersionedTx", + "type_name": "Transaction", "variants": [ { "name": "V0", "discriminant": 0, "template": null, "value": { - "ByIndex": 2 + "ByIndex": 1 + } + }, + { + "name": "V1", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 172 } } ], @@ -40,7 +31,7 @@ "fields": [ { "value": { - "ByIndex": 3 + "ByIndex": 2 }, "silent": false, "doc": "" @@ -58,7 +49,12 @@ "display_name": "signature", "silent": false, "value": { - "ByIndex": 4 + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } }, "doc": "" }, @@ -66,7 +62,12 @@ "display_name": "pub_key", "silent": false, "value": { - "ByIndex": 5 + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } }, "doc": "" }, @@ -74,20 +75,15 @@ "display_name": "runtime_call", "silent": false, "value": { - "ByIndex": 6 + "ByIndex": 3 }, "doc": "" }, { - "display_name": "generation", + "display_name": "uniqueness", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 167 }, "doc": "" }, @@ -95,51 +91,7 @@ "display_name": "details", "silent": false, "value": { - "ByIndex": 111 - }, - "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "Ed25519Signature", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "msg_sig", - "silent": false, - "value": { - "Immediate": { - "ByteArray": { - "len": 64, - "display": "Hex" - } - } - }, - "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "Ed25519PublicKey", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "pub_key", - "silent": false, - "value": { - "Immediate": { - "ByteArray": { - "len": 32, - "display": "Hex" - } - } + "ByIndex": 170 }, "doc": "" } @@ -155,7 +107,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 7 + "ByIndex": 4 } }, { @@ -163,7 +115,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 25 + "ByIndex": 27 } }, { @@ -171,7 +123,7 @@ "discriminant": 2, "template": null, "value": { - "ByIndex": 32 + "ByIndex": 34 } }, { @@ -179,7 +131,7 @@ "discriminant": 3, "template": null, "value": { - "ByIndex": 35 + "ByIndex": 37 } }, { @@ -187,7 +139,7 @@ "discriminant": 4, "template": null, "value": { - "ByIndex": 40 + "ByIndex": 42 } }, { @@ -195,7 +147,7 @@ "discriminant": 5, "template": null, "value": { - "ByIndex": 44 + "ByIndex": 46 } }, { @@ -203,7 +155,7 @@ "discriminant": 6, "template": null, "value": { - "ByIndex": 49 + "ByIndex": 54 } }, { @@ -211,7 +163,7 @@ "discriminant": 7, "template": null, "value": { - "ByIndex": 51 + "ByIndex": 56 } }, { @@ -219,7 +171,7 @@ "discriminant": 8, "template": null, "value": { - "ByIndex": 52 + "ByIndex": 59 } }, { @@ -227,31 +179,71 @@ "discriminant": 9, "template": null, "value": { - "ByIndex": 53 + "ByIndex": 60 } }, { - "name": "Evm", + "name": "RevenueShare", "discriminant": 10, "template": null, "value": { - "ByIndex": 82 + "ByIndex": 89 } }, { - "name": "AccessPattern", + "name": "Mailbox", "discriminant": 11, "template": null, "value": { - "ByIndex": 85 + "ByIndex": 94 } }, { - "name": "SyntheticLoad", + "name": "InterchainGasPaymaster", "discriminant": 12, "template": null, "value": { - "ByIndex": 106 + "ByIndex": 103 + } + }, + { + "name": "MerkleTreeHook", + "discriminant": 13, + "template": null, + "value": { + "ByIndex": 113 + } + }, + { + "name": "Warp", + "discriminant": 14, + "template": null, + "value": { + "ByIndex": 114 + } + }, + { + "name": "Evm", + "discriminant": 15, + "template": null, + "value": { + "ByIndex": 134 + } + }, + { + "name": "AccessPattern", + "discriminant": 16, + "template": null, + "value": { + "ByIndex": 148 + } + }, + { + "name": "SyntheticLoad", + "discriminant": 17, + "template": null, + "value": { + "ByIndex": 162 } } ], @@ -265,7 +257,7 @@ "fields": [ { "value": { - "ByIndex": 8 + "ByIndex": 5 }, "silent": false, "doc": "" @@ -282,7 +274,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 9 + "ByIndex": 6 } }, { @@ -290,7 +282,7 @@ "discriminant": 1, "template": "Transfer to address {} {}.", "value": { - "ByIndex": 19 + "ByIndex": 18 } }, { @@ -298,7 +290,7 @@ "discriminant": 2, "template": null, "value": { - "ByIndex": 22 + "ByIndex": 21 } }, { @@ -306,16 +298,32 @@ "discriminant": 3, "template": null, "value": { - "ByIndex": 23 + "ByIndex": 22 } }, { "name": "Freeze", "discriminant": 4, "template": null, + "value": { + "ByIndex": 23 + } + }, + { + "name": "UpdateAdmin", + "discriminant": 5, + "template": null, "value": { "ByIndex": 24 } + }, + { + "name": "TransferWithMemo", + "discriminant": 6, + "template": "Transfer to address {} {} with memo `{}`.", + "value": { + "ByIndex": 26 + } } ], "hide_tag": false @@ -339,7 +347,7 @@ "display_name": "token_decimals", "silent": false, "value": { - "ByIndex": 10 + "ByIndex": 7 }, "doc": "" }, @@ -347,7 +355,7 @@ "display_name": "initial_balance", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" }, @@ -355,7 +363,7 @@ "display_name": "mint_to_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -363,7 +371,7 @@ "display_name": "admins", "silent": false, "value": { - "ByIndex": 17 + "ByIndex": 16 }, "doc": "" }, @@ -371,7 +379,7 @@ "display_name": "supply_cap", "silent": false, "value": { - "ByIndex": 18 + "ByIndex": 17 }, "doc": "" } @@ -412,22 +420,30 @@ }, { "Enum": { - "type_name": "MultiAddress", + "type_name": "MultiAddressEvmSolana", "variants": [ { "name": "Standard", "discriminant": 0, "template": null, "value": { - "ByIndex": 13 + "ByIndex": 10 } }, { - "name": "Vm", + "name": "Evm", "discriminant": 1, "template": null, "value": { - "ByIndex": 15 + "ByIndex": 12 + } + }, + { + "name": "Solana", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 14 } } ], @@ -441,7 +457,7 @@ "fields": [ { "value": { - "ByIndex": 14 + "ByIndex": 11 }, "silent": false, "doc": "" @@ -480,7 +496,7 @@ "fields": [ { "value": { - "ByIndex": 16 + "ByIndex": 13 }, "silent": false, "doc": "" @@ -508,17 +524,52 @@ ] } }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 15 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Base58" + } + } + }, + "silent": false, + "doc": "" + } + ] + } + }, { "Vec": { "value": { - "ByIndex": 12 + "ByIndex": 9 } } }, { "Option": { "value": { - "ByIndex": 11 + "ByIndex": 8 } } }, @@ -532,7 +583,7 @@ "display_name": "to", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -540,7 +591,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" } @@ -577,7 +628,7 @@ "display_name": "token_id", "silent": false, "value": { - "ByIndex": 21 + "ByIndex": 20 }, "doc": "" } @@ -618,7 +669,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" } @@ -635,7 +686,7 @@ "display_name": "coins", "silent": false, "value": { - "ByIndex": 20 + "ByIndex": 19 }, "doc": "" }, @@ -643,7 +694,7 @@ "display_name": "mint_to_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -660,7 +711,7 @@ "display_name": "token_id", "silent": false, "value": { - "ByIndex": 21 + "ByIndex": 20 }, "doc": "" } @@ -668,20 +719,85 @@ } }, { - "Tuple": { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_UpdateAdmin", "template": null, "peekable": false, "fields": [ { + "display_name": "new_admin", + "silent": false, "value": { - "ByIndex": 26 + "ByIndex": 25 }, - "silent": false, "doc": "" - } - ] - } - }, + }, + { + "display_name": "token_id", + "silent": false, + "value": { + "ByIndex": 20 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 9 + } + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_TransferWithMemo", + "template": "Transfer to address {} {} with memo `{}`.", + "peekable": false, + "fields": [ + { + "display_name": "to", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "coins", + "silent": false, + "value": { + "ByIndex": 19 + }, + "doc": "" + }, + { + "display_name": "memo", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 28 + }, + "silent": false, + "doc": "" + } + ] + } + }, { "Enum": { "type_name": "CallMessage", @@ -691,7 +807,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 27 + "ByIndex": 29 } }, { @@ -699,7 +815,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 29 + "ByIndex": 31 } }, { @@ -707,7 +823,7 @@ "discriminant": 2, "template": null, "value": { - "ByIndex": 30 + "ByIndex": 32 } }, { @@ -715,7 +831,7 @@ "discriminant": 3, "template": null, "value": { - "ByIndex": 31 + "ByIndex": 33 } } ], @@ -732,7 +848,7 @@ "display_name": "da_address", "silent": false, "value": { - "ByIndex": 28 + "ByIndex": 30 }, "doc": "" }, @@ -740,7 +856,7 @@ "display_name": "amount", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" } @@ -777,7 +893,7 @@ "display_name": "da_address", "silent": false, "value": { - "ByIndex": 28 + "ByIndex": 30 }, "doc": "" }, @@ -785,7 +901,7 @@ "display_name": "amount", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 8 }, "doc": "" } @@ -802,7 +918,7 @@ "display_name": "da_address", "silent": false, "value": { - "ByIndex": 28 + "ByIndex": 30 }, "doc": "" } @@ -819,7 +935,7 @@ "display_name": "da_address", "silent": false, "value": { - "ByIndex": 28 + "ByIndex": 30 }, "doc": "" } @@ -833,7 +949,7 @@ "fields": [ { "value": { - "ByIndex": 33 + "ByIndex": 35 }, "silent": false, "doc": "" @@ -850,7 +966,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 34 + "ByIndex": 36 } } ], @@ -867,7 +983,7 @@ "display_name": "new_reward_address", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -881,7 +997,7 @@ "fields": [ { "value": { - "ByIndex": 36 + "ByIndex": 38 }, "silent": false, "doc": "" @@ -898,7 +1014,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 37 + "ByIndex": 39 } }, { @@ -918,7 +1034,7 @@ "discriminant": 3, "template": null, "value": { - "ByIndex": 38 + "ByIndex": 40 } }, { @@ -932,7 +1048,7 @@ "discriminant": 5, "template": null, "value": { - "ByIndex": 39 + "ByIndex": 41 } } ], @@ -946,7 +1062,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -961,7 +1077,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -976,7 +1092,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -991,7 +1107,7 @@ "fields": [ { "value": { - "ByIndex": 41 + "ByIndex": 43 }, "silent": false, "doc": "" @@ -1008,7 +1124,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 42 + "ByIndex": 44 } }, { @@ -1016,7 +1132,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 43 + "ByIndex": 45 } }, { @@ -1036,7 +1152,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1051,7 +1167,7 @@ "fields": [ { "value": { - "ByIndex": 11 + "ByIndex": 8 }, "silent": false, "doc": "" @@ -1066,7 +1182,7 @@ "fields": [ { "value": { - "ByIndex": 45 + "ByIndex": 47 }, "silent": false, "doc": "" @@ -1083,7 +1199,31 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 46 + "ByIndex": 48 + } + }, + { + "name": "AddCredentialToAddress", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 51 + } + }, + { + "name": "RemoveCredentialFromAddress", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 52 + } + }, + { + "name": "RotateCredentialOnAddress", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 53 } } ], @@ -1097,7 +1237,7 @@ "fields": [ { "value": { - "ByIndex": 47 + "ByIndex": 49 }, "silent": false, "doc": "" @@ -1112,7 +1252,7 @@ "fields": [ { "value": { - "ByIndex": 48 + "ByIndex": 50 }, "silent": false, "doc": "" @@ -1141,37 +1281,83 @@ } }, { - "Tuple": { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_AddCredentialToAddress", "template": null, "peekable": false, "fields": [ { + "display_name": "address", + "silent": false, "value": { - "ByIndex": 50 + "ByIndex": 9 }, + "doc": "" + }, + { + "display_name": "credential", "silent": false, + "value": { + "ByIndex": 49 + }, "doc": "" } ] } }, { - "Enum": { - "type_name": "NotInstantiable", - "variants": [], - "hide_tag": false + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RemoveCredentialFromAddress", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "credential", + "silent": false, + "value": { + "ByIndex": 49 + }, + "doc": "" + } + ] } }, { - "Tuple": { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RotateCredentialOnAddress", "template": null, "peekable": false, "fields": [ { + "display_name": "address", + "silent": false, "value": { - "ByIndex": 50 + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "old_credential", + "silent": false, + "value": { + "ByIndex": 49 }, + "doc": "" + }, + { + "display_name": "new_credential", "silent": false, + "value": { + "ByIndex": 49 + }, "doc": "" } ] @@ -1184,7 +1370,7 @@ "fields": [ { "value": { - "ByIndex": 50 + "ByIndex": 55 }, "silent": false, "doc": "" @@ -1192,6 +1378,13 @@ ] } }, + { + "Enum": { + "type_name": "NotInstantiable", + "variants": [], + "hide_tag": false + } + }, { "Tuple": { "template": null, @@ -1199,7 +1392,7 @@ "fields": [ { "value": { - "ByIndex": 54 + "ByIndex": 57 }, "silent": false, "doc": "" @@ -1212,27 +1405,17 @@ "type_name": "CallMessage", "variants": [ { - "name": "RegisterPaymaster", + "name": "TerminateSetupMode", "discriminant": 0, "template": null, - "value": { - "ByIndex": 55 - } + "value": null }, { - "name": "SetPayerForSequencer", + "name": "SetOracleTime", "discriminant": 1, "template": null, "value": { - "ByIndex": 71 - } - }, - { - "name": "UpdatePolicy", - "discriminant": 2, - "template": null, - "value": { - "ByIndex": 72 + "ByIndex": 58 } } ], @@ -1241,15 +1424,20 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", + "type_name": "__SovVirtualWallet_CallMessage_SetOracleTime", "template": null, "peekable": false, "fields": [ { - "display_name": "policy", + "display_name": "milliseconds_since_epoch", "silent": false, "value": { - "ByIndex": 56 + "Immediate": { + "Integer": [ + "i64", + "Decimal" + ] + } }, "doc": "" } @@ -1257,32 +1445,111 @@ } }, { - "Struct": { - "type_name": "PaymasterPolicyInitializer", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "default_payee_policy", - "silent": false, "value": { - "ByIndex": 57 + "ByIndex": 55 }, + "silent": false, "doc": "" - }, + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ { - "display_name": "payees", - "silent": false, "value": { - "ByIndex": 66 + "ByIndex": 61 }, - "doc": "" - }, - { - "display_name": "authorized_updaters", "silent": false, - "value": { - "ByIndex": 17 + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "RegisterPaymaster", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 62 + } + }, + { + "name": "SetPayerForSequencer", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 78 + } + }, + { + "name": "UpdatePolicy", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 79 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "policy", + "silent": false, + "value": { + "ByIndex": 63 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "PaymasterPolicyInitializer", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "default_payee_policy", + "silent": false, + "value": { + "ByIndex": 64 + }, + "doc": "" + }, + { + "display_name": "payees", + "silent": false, + "value": { + "ByIndex": 73 + }, + "doc": "" + }, + { + "display_name": "authorized_updaters", + "silent": false, + "value": { + "ByIndex": 16 }, "doc": "" }, @@ -1290,7 +1557,7 @@ "display_name": "authorized_sequencers", "silent": false, "value": { - "ByIndex": 68 + "ByIndex": 75 }, "doc": "" } @@ -1306,7 +1573,7 @@ "discriminant": 0, "template": null, "value": { - "ByIndex": 58 + "ByIndex": 65 } }, { @@ -1329,7 +1596,7 @@ "display_name": "max_fee", "silent": false, "value": { - "ByIndex": 18 + "ByIndex": 17 }, "doc": "" }, @@ -1337,7 +1604,7 @@ "display_name": "gas_limit", "silent": false, "value": { - "ByIndex": 59 + "ByIndex": 66 }, "doc": "" }, @@ -1345,7 +1612,7 @@ "display_name": "max_gas_price", "silent": false, "value": { - "ByIndex": 62 + "ByIndex": 69 }, "doc": "" }, @@ -1353,7 +1620,7 @@ "display_name": "transaction_limit", "silent": false, "value": { - "ByIndex": 65 + "ByIndex": 72 }, "doc": "" } @@ -1363,7 +1630,7 @@ { "Option": { "value": { - "ByIndex": 60 + "ByIndex": 67 } } }, @@ -1374,7 +1641,7 @@ "fields": [ { "value": { - "ByIndex": 61 + "ByIndex": 68 }, "silent": false, "doc": "" @@ -1398,7 +1665,7 @@ { "Option": { "value": { - "ByIndex": 63 + "ByIndex": 70 } } }, @@ -1412,7 +1679,7 @@ "display_name": "value", "silent": false, "value": { - "ByIndex": 64 + "ByIndex": 71 }, "doc": "" } @@ -1423,7 +1690,7 @@ "Array": { "len": 2, "value": { - "ByIndex": 11 + "ByIndex": 8 } } }, @@ -1442,7 +1709,7 @@ { "Vec": { "value": { - "ByIndex": 67 + "ByIndex": 74 } } }, @@ -1453,14 +1720,14 @@ "fields": [ { "value": { - "ByIndex": 12 + "ByIndex": 9 }, "silent": false, "doc": "" }, { "value": { - "ByIndex": 57 + "ByIndex": 64 }, "silent": false, "doc": "" @@ -1483,7 +1750,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 69 + "ByIndex": 76 } } ], @@ -1497,7 +1764,7 @@ "fields": [ { "value": { - "ByIndex": 70 + "ByIndex": 77 }, "silent": false, "doc": "" @@ -1508,7 +1775,7 @@ { "Vec": { "value": { - "ByIndex": 28 + "ByIndex": 30 } } }, @@ -1522,7 +1789,7 @@ "display_name": "payer", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" } @@ -1539,7 +1806,7 @@ "display_name": "payer", "silent": false, "value": { - "ByIndex": 12 + "ByIndex": 9 }, "doc": "" }, @@ -1547,7 +1814,7 @@ "display_name": "update", "silent": false, "value": { - "ByIndex": 73 + "ByIndex": 80 }, "doc": "" } @@ -1564,7 +1831,7 @@ "display_name": "sequencer_update", "silent": false, "value": { - "ByIndex": 74 + "ByIndex": 81 }, "doc": "" }, @@ -1572,7 +1839,7 @@ "display_name": "updaters_to_add", "silent": false, "value": { - "ByIndex": 79 + "ByIndex": 86 }, "doc": "" }, @@ -1580,7 +1847,7 @@ "display_name": "updaters_to_remove", "silent": false, "value": { - "ByIndex": 79 + "ByIndex": 86 }, "doc": "" }, @@ -1588,7 +1855,7 @@ "display_name": "payee_policies_to_set", "silent": false, "value": { - "ByIndex": 80 + "ByIndex": 87 }, "doc": "" }, @@ -1596,7 +1863,7 @@ "display_name": "payee_policies_to_delete", "silent": false, "value": { - "ByIndex": 79 + "ByIndex": 86 }, "doc": "" }, @@ -1604,7 +1871,7 @@ "display_name": "default_policy", "silent": false, "value": { - "ByIndex": 81 + "ByIndex": 88 }, "doc": "" } @@ -1614,7 +1881,7 @@ { "Option": { "value": { - "ByIndex": 75 + "ByIndex": 82 } } }, @@ -1633,7 +1900,7 @@ "discriminant": 1, "template": null, "value": { - "ByIndex": 76 + "ByIndex": 83 } } ], @@ -1647,7 +1914,7 @@ "fields": [ { "value": { - "ByIndex": 77 + "ByIndex": 84 }, "silent": false, "doc": "" @@ -1665,7 +1932,7 @@ "display_name": "to_add", "silent": false, "value": { - "ByIndex": 78 + "ByIndex": 85 }, "doc": "" }, @@ -1673,7 +1940,7 @@ "display_name": "to_remove", "silent": false, "value": { - "ByIndex": 78 + "ByIndex": 85 }, "doc": "" } @@ -1683,28 +1950,28 @@ { "Option": { "value": { - "ByIndex": 70 + "ByIndex": 77 } } }, { "Option": { "value": { - "ByIndex": 17 + "ByIndex": 16 } } }, { "Option": { "value": { - "ByIndex": 66 + "ByIndex": 73 } } }, { "Option": { "value": { - "ByIndex": 57 + "ByIndex": 64 } } }, @@ -1715,7 +1982,7 @@ "fields": [ { "value": { - "ByIndex": 83 + "ByIndex": 90 }, "silent": false, "doc": "" @@ -1724,16 +1991,65 @@ } }, { - "Struct": { + "Enum": { "type_name": "CallMessage", + "variants": [ + { + "name": "ActivateRevenueShare", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "DeactivateRevenueShare", + "discriminant": 1, + "template": null, + "value": null + }, + { + "name": "LowerRevenuePercentage", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 91 + } + }, + { + "name": "UpdateSovereignAdmin", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 92 + } + }, + { + "name": "WithdrawRewards", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 93 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_LowerRevenuePercentage", "template": null, "peekable": false, "fields": [ { - "display_name": "rlp", + "display_name": "percentage_in_basis_points", "silent": false, "value": { - "ByIndex": 84 + "Immediate": { + "Integer": [ + "u16", + "Decimal" + ] + } }, "doc": "" } @@ -1742,19 +2058,32 @@ }, { "Struct": { - "type_name": "RlpEvmTransaction", + "type_name": "__SovVirtualWallet_CallMessage_UpdateSovereignAdmin", "template": null, "peekable": false, "fields": [ { - "display_name": "rlp", + "display_name": "new_admin", "silent": false, "value": { - "Immediate": { - "ByteVec": { - "display": "Hex" - } - } + "ByIndex": 9 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_WithdrawRewards", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "token_id", + "silent": false, + "value": { + "ByIndex": 20 }, "doc": "" } @@ -1768,7 +2097,7 @@ "fields": [ { "value": { - "ByIndex": 86 + "ByIndex": 95 }, "silent": false, "doc": "" @@ -1778,162 +2107,92 @@ }, { "Enum": { - "type_name": "AccessPatternMessages", + "type_name": "CallMessage", "variants": [ { - "name": "WriteCells", + "name": "Dispatch", "discriminant": 0, "template": null, "value": { - "ByIndex": 87 + "ByIndex": 96 } }, { - "name": "WriteCustom", + "name": "Process", "discriminant": 1, "template": null, "value": { - "ByIndex": 88 + "ByIndex": 99 } }, { - "name": "ReadCells", + "name": "Announce", "discriminant": 2, "template": null, "value": { - "ByIndex": 90 + "ByIndex": 100 } - }, + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_Dispatch", + "template": null, + "peekable": false, + "fields": [ { - "name": "HashBytes", - "discriminant": 3, - "template": null, + "display_name": "domain", + "silent": false, "value": { - "ByIndex": 91 - } + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" }, { - "name": "HashCustom", - "discriminant": 4, - "template": null, + "display_name": "recipient", + "silent": false, "value": { - "ByIndex": 92 - } + "ByIndex": 50 + }, + "doc": "" }, { - "name": "StoreSignature", - "discriminant": 5, - "template": null, - "value": { - "ByIndex": 93 - } - }, - { - "name": "VerifySignature", - "discriminant": 6, - "template": null, - "value": null - }, - { - "name": "VerifyCustomSignature", - "discriminant": 7, - "template": null, - "value": { - "ByIndex": 94 - } - }, - { - "name": "StoreSerializedString", - "discriminant": 8, - "template": null, - "value": { - "ByIndex": 95 - } - }, - { - "name": "DeserializeBytesAsString", - "discriminant": 9, - "template": null, - "value": null - }, - { - "name": "DeserializeCustomString", - "discriminant": 10, - "template": null, - "value": { - "ByIndex": 96 - } - }, - { - "name": "DeleteCells", - "discriminant": 11, - "template": null, + "display_name": "body", + "silent": false, "value": { "ByIndex": 97 - } - }, - { - "name": "SetHook", - "discriminant": 12, - "template": null, - "value": { - "ByIndex": 98 - } + }, + "doc": "" }, { - "name": "UpdateAdmin", - "discriminant": 13, - "template": null, - "value": { - "ByIndex": 105 - } - } - ], - "hide_tag": false - } - }, - { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCells", - "template": null, - "peekable": false, - "fields": [ - { - "display_name": "begin", + "display_name": "metadata", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 98 }, "doc": "" }, { - "display_name": "num_cells", + "display_name": "relayer", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 25 }, "doc": "" }, { - "display_name": "data_size", + "display_name": "gas_payment_limit", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] - } + "ByIndex": 8 }, "doc": "" } @@ -1941,71 +2200,50 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCustom", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "begin", - "silent": false, "value": { "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] + "ByteVec": { + "display": "Hex" + } } }, - "doc": "" - }, - { - "display_name": "content", "silent": false, - "value": { - "ByIndex": 89 - }, "doc": "" } ] } }, { - "Vec": { + "Option": { "value": { - "Immediate": "String" + "ByIndex": 97 } } }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_ReadCells", + "type_name": "__SovVirtualWallet_CallMessage_Process", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "metadata", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 97 }, "doc": "" }, { - "display_name": "num_cells", + "display_name": "message", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 97 }, "doc": "" } @@ -2014,141 +2252,196 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_HashBytes", + "type_name": "__SovVirtualWallet_CallMessage_Announce", "template": null, "peekable": false, "fields": [ { - "display_name": "filler", + "display_name": "validator_address", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u8", - "Decimal" - ] - } + "ByIndex": 101 }, "doc": "" }, { - "display_name": "size", + "display_name": "storage_location", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + }, + { + "display_name": "signature", "silent": false, + "value": { + "ByIndex": 102 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { "value": { "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] + "ByteArray": { + "len": 20, + "display": "Hex" + } } }, + "silent": false, "doc": "" } ] } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_HashCustom", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "input", - "silent": false, "value": { "Immediate": { - "ByteVec": { + "ByteArray": { + "len": 65, "display": "Hex" } } }, + "silent": false, "doc": "" } ] } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSignature", + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "sign", - "silent": false, "value": { - "ByIndex": 4 + "ByIndex": 104 }, + "silent": false, "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "SetRelayerConfig", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 105 + } }, { - "display_name": "pub_key", - "silent": false, + "name": "UpdateOracleData", + "discriminant": 1, + "template": null, "value": { - "ByIndex": 5 - }, - "doc": "" + "ByIndex": 111 + } }, { - "display_name": "message", - "silent": false, + "name": "ClaimRewards", + "discriminant": 2, + "template": null, "value": { - "Immediate": "String" - }, - "doc": "" + "ByIndex": 112 + } } - ] + ], + "hide_tag": false } }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_VerifyCustomSignature", + "type_name": "__SovVirtualWallet_CallMessage_SetRelayerConfig", "template": null, "peekable": false, "fields": [ { - "display_name": "sign", + "display_name": "domain_oracle_data", "silent": false, "value": { - "ByIndex": 4 + "ByIndex": 106 }, "doc": "" }, { - "display_name": "pub_key", + "display_name": "domain_default_gas", "silent": false, "value": { - "ByIndex": 5 + "ByIndex": 109 }, "doc": "" }, { - "display_name": "message", + "display_name": "default_gas", "silent": false, "value": { - "Immediate": "String" + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "beneficiary", + "silent": false, + "value": { + "ByIndex": 25 }, "doc": "" } ] } }, + { + "Vec": { + "value": { + "ByIndex": 107 + } + } + }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSerializedString", + "type_name": "DomainOracleData", "template": null, "peekable": false, "fields": [ { - "display_name": "input", + "display_name": "domain", "silent": false, "value": { "Immediate": { - "ByteVec": { - "display": "Hex" - } - } + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "data_value", + "silent": false, + "value": { + "ByIndex": 108 }, "doc": "" } @@ -2157,18 +2450,27 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_DeserializeCustomString", + "type_name": "ExchangeRateAndGasPrice", "template": null, "peekable": false, "fields": [ { - "display_name": "input", + "display_name": "gas_price", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "token_exchange_rate", "silent": false, "value": { "Immediate": { - "ByteVec": { - "display": "Hex" - } + "Integer": [ + "u128", + "Decimal" + ] } }, "doc": "" @@ -2176,19 +2478,26 @@ ] } }, + { + "Vec": { + "value": { + "ByIndex": 110 + } + } + }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_DeleteCells", + "type_name": "DomainDefaultGas", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2196,15 +2505,10 @@ "doc": "" }, { - "display_name": "num_cells", + "display_name": "default_gas", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 8 }, "doc": "" } @@ -2213,23 +2517,28 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_SetHook", + "type_name": "__SovVirtualWallet_CallMessage_UpdateOracleData", "template": null, "peekable": false, "fields": [ { - "display_name": "pre", + "display_name": "domain", "silent": false, "value": { - "ByIndex": 99 + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } }, "doc": "" }, { - "display_name": "post", + "display_name": "oracle_data", "silent": false, "value": { - "ByIndex": 99 + "ByIndex": 108 }, "doc": "" } @@ -2237,45 +2546,98 @@ } }, { - "Option": { - "value": { - "ByIndex": 100 - } + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_ClaimRewards", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "relayer_address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + } + ] } }, { - "Vec": { - "value": { - "ByIndex": 101 - } + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Skip": { + "len": 0 + } + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 115 + }, + "silent": false, + "doc": "" + } + ] } }, { "Enum": { - "type_name": "HooksConfig", + "type_name": "CallMessage", "variants": [ { - "name": "Read", + "name": "Register", "discriminant": 0, "template": null, "value": { - "ByIndex": 102 + "ByIndex": 116 } }, { - "name": "Write", + "name": "Update", "discriminant": 1, "template": null, "value": { - "ByIndex": 103 + "ByIndex": 128 } }, { - "name": "Delete", + "name": "EnrollRemoteRouter", "discriminant": 2, "template": null, "value": { - "ByIndex": 104 + "ByIndex": 131 + } + }, + { + "name": "UnEnrollRemoteRouter", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 132 + } + }, + { + "name": "TransferRemote", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 133 } } ], @@ -2284,116 +2646,71 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Read", + "type_name": "__SovVirtualWallet_CallMessage_Register", "template": null, "peekable": false, "fields": [ { - "display_name": "begin", + "display_name": "admin", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 117 }, "doc": "" }, { - "display_name": "size", + "display_name": "token_source", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 119 }, "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Write", - "template": null, - "peekable": false, - "fields": [ + }, { - "display_name": "begin", + "display_name": "ism", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 122 }, "doc": "" }, { - "display_name": "size", + "display_name": "remote_routers", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 126 }, "doc": "" }, { - "display_name": "data_size", + "display_name": "inbound_transferrable_tokens_limit", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u32", - "Decimal" - ] - } + "ByIndex": 8 }, "doc": "" - } - ] - } - }, - { - "Struct": { - "type_name": "__SovVirtualWallet_HooksConfig_Delete", - "template": null, - "peekable": false, - "fields": [ + }, { - "display_name": "begin", + "display_name": "inbound_limit_replenishment_per_slot", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 8 }, "doc": "" }, { - "display_name": "size", + "display_name": "outbound_transferrable_tokens_limit", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "outbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 8 }, "doc": "" } @@ -2401,20 +2718,25 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", - "template": null, - "peekable": false, - "fields": [ + "Enum": { + "type_name": "Admin", + "variants": [ { - "display_name": "new_admin", - "silent": false, + "name": "None", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "InsecureOwner", + "discriminant": 1, + "template": null, "value": { - "ByIndex": 12 - }, - "doc": "" + "ByIndex": 118 + } } - ] + ], + "hide_tag": false } }, { @@ -2424,7 +2746,7 @@ "fields": [ { "value": { - "ByIndex": 107 + "ByIndex": 9 }, "silent": false, "doc": "" @@ -2434,31 +2756,29 @@ }, { "Enum": { - "type_name": "CallMessage", + "type_name": "TokenKind", "variants": [ { - "name": "ReadAndSetManyIndividualValues", + "name": "Synthetic", "discriminant": 0, "template": null, "value": { - "ByIndex": 108 + "ByIndex": 120 } }, { - "name": "ReadAndSetHeavyState", + "name": "Collateral", "discriminant": 1, "template": null, "value": { - "ByIndex": 109 + "ByIndex": 121 } }, { - "name": "RunCPUHeavyOperation", + "name": "Native", "discriminant": 2, "template": null, - "value": { - "ByIndex": 110 - } + "value": null } ], "hide_tag": false @@ -2466,78 +2786,127 @@ }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "type_name": "__SovVirtualWallet_TokenKind_Synthetic", "template": null, "peekable": false, "fields": [ { - "display_name": "number_of_operations", + "display_name": "remote_token_id", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 50 }, "doc": "" }, { - "display_name": "salt", + "display_name": "remote_decimals", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u8", "Decimal" ] } }, "doc": "" + }, + { + "display_name": "local_decimals", + "silent": false, + "value": { + "ByIndex": 7 + }, + "doc": "" } ] } }, { "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "type_name": "__SovVirtualWallet_TokenKind_Collateral", "template": null, "peekable": false, "fields": [ { - "display_name": "number_of_new_values", + "display_name": "token", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 20 }, "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "Ism", + "variants": [ + { + "name": "AlwaysTrust", + "discriminant": 0, + "template": null, + "value": null }, { - "display_name": "max_heavy_state_size", + "name": "TrustedRelayer", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 123 + } + }, + { + "name": "MessageIdMultisig", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 124 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_Ism_TrustedRelayer", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "relayer", "silent": false, "value": { - "Immediate": { - "Integer": [ - "u64", - "Decimal" - ] - } + "ByIndex": 50 + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_Ism_MessageIdMultisig", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "validators", + "silent": false, + "value": { + "ByIndex": 125 }, "doc": "" }, { - "display_name": "salt", + "display_name": "threshold", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2548,22 +2917,41 @@ } }, { - "Struct": { - "type_name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "Vec": { + "value": { + "ByIndex": 101 + } + } + }, + { + "Vec": { + "value": { + "ByIndex": 127 + } + } + }, + { + "Tuple": { "template": null, "peekable": false, "fields": [ { - "display_name": "iterations", - "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } }, + "silent": false, + "doc": "" + }, + { + "value": { + "ByIndex": 50 + }, + "silent": false, "doc": "" } ] @@ -2571,65 +2959,146 @@ }, { "Struct": { - "type_name": "TxDetails", + "type_name": "__SovVirtualWallet_CallMessage_Update", "template": null, "peekable": false, "fields": [ { - "display_name": "max_priority_fee_bips", + "display_name": "warp_route", "silent": false, "value": { - "ByIndex": 112 + "ByIndex": 50 }, "doc": "" }, { - "display_name": "max_fee", + "display_name": "admin", "silent": false, "value": { - "ByIndex": 11 + "ByIndex": 129 }, "doc": "" }, { - "display_name": "gas_limit", + "display_name": "ism", "silent": false, "value": { - "ByIndex": 59 + "ByIndex": 130 }, "doc": "" }, { - "display_name": "chain_id", + "display_name": "inbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "inbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "outbound_transferrable_tokens_limit", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + }, + { + "display_name": "outbound_limit_replenishment_per_slot", + "silent": false, + "value": { + "ByIndex": 17 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 117 + } + } + }, + { + "Option": { + "value": { + "ByIndex": 122 + } + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_EnrollRemoteRouter", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "warp_route", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "remote_domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } }, "doc": "" + }, + { + "display_name": "remote_router_address", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" } ] } }, { - "Tuple": { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_UnEnrollRemoteRouter", "template": null, "peekable": false, "fields": [ { + "display_name": "warp_route", + "silent": false, + "value": { + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "remote_domain", + "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } }, - "silent": false, "doc": "" } ] @@ -2637,25 +3106,25 @@ }, { "Struct": { - "type_name": "UnsignedTransaction", + "type_name": "__SovVirtualWallet_CallMessage_TransferRemote", "template": null, "peekable": false, "fields": [ { - "display_name": "runtime_call", + "display_name": "warp_route", "silent": false, "value": { - "ByIndex": 6 + "ByIndex": 50 }, "doc": "" }, { - "display_name": "generation", + "display_name": "destination_domain", "silent": false, "value": { "Immediate": { "Integer": [ - "u64", + "u32", "Decimal" ] } @@ -2663,95 +3132,1761 @@ "doc": "" }, { - "display_name": "details", + "display_name": "recipient", "silent": false, "value": { - "ByIndex": 111 + "ByIndex": 50 + }, + "doc": "" + }, + { + "display_name": "amount", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "relayer", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + }, + { + "display_name": "gas_payment_limit", + "silent": false, + "value": { + "ByIndex": 8 }, "doc": "" } ] } - } - ], - "root_type_indices": [ - 0, - 113, - 6, - 14 - ], - "chain_data": { - "chain_id": 4321, - "chain_name": "TestChain" - }, - "templates": [ - {}, - {}, + }, { - "transfer": { - "preencoded_bytes": [ - 0, - 1 - ], - "inputs": [ - [ - "to", - { - "type_link": { - "ByIndex": 12 - }, - "offset": 2 + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 135 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "Call", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 136 } - ], - [ - "amount", - { - "type_link": { - "Immediate": { - "Integer": [ - "u128", - { - "FixedPoint": { - "FromSiblingField": { - "field_index": 1, - "byte_offset": 31 - } - } - } - ] - } - }, - "offset": 2 + }, + { + "name": "UpdateRuntimeConfig", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 138 } - ], - [ - "token_id", - { - "type_link": { - "ByIndex": 21 - }, - "offset": 2 + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 137 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "RlpEvmTransaction", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "rlp", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 139 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "EvmRuntimeConfigUpdate", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_hardfork", + "silent": false, + "value": { + "ByIndex": 140 + }, + "doc": "" + }, + { + "display_name": "new_contract_creation_policy", + "silent": false, + "value": { + "ByIndex": 142 + }, + "doc": "" + }, + { + "display_name": "chain_spec_update", + "silent": false, + "value": { + "ByIndex": 145 + }, + "doc": "" + }, + { + "display_name": "new_admin", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 141 + } + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + }, + { + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 143 + } + } + }, + { + "Enum": { + "type_name": "ContractCreationPolicyUpdate", + "variants": [ + { + "name": "Everyone", + "discriminant": 0, + "template": null, + "value": null + }, + { + "name": "Allowlist", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 144 } - ] + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_ContractCreationPolicyUpdate_Allowlist", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "add", + "silent": false, + "value": { + "ByIndex": 125 + }, + "doc": "" + }, + { + "display_name": "remove", + "silent": false, + "value": { + "ByIndex": 125 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "ByIndex": 146 + } + } + }, + { + "Struct": { + "type_name": "ChainSpecUpdate", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_limit_contract_code_size", + "silent": false, + "value": { + "ByIndex": 147 + }, + "doc": "" + }, + { + "display_name": "new_block_gas_limit", + "silent": false, + "value": { + "ByIndex": 72 + }, + "doc": "" + }, + { + "display_name": "new_tx_gas_limit", + "silent": false, + "value": { + "ByIndex": 72 + }, + "doc": "" + } + ] + } + }, + { + "Option": { + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + } + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 149 + }, + "silent": false, + "doc": "" + } ] } }, - {} - ], - "serde_metadata": [ { - "name": "Transaction", + "Enum": { + "type_name": "AccessPatternMessages", + "variants": [ + { + "name": "WriteCells", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 150 + } + }, + { + "name": "WriteCustom", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 151 + } + }, + { + "name": "ReadCells", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 153 + } + }, + { + "name": "HashBytes", + "discriminant": 3, + "template": null, + "value": { + "ByIndex": 154 + } + }, + { + "name": "HashCustom", + "discriminant": 4, + "template": null, + "value": { + "ByIndex": 155 + } + }, + { + "name": "StoreSignature", + "discriminant": 5, + "template": null, + "value": { + "ByIndex": 156 + } + }, + { + "name": "VerifySignature", + "discriminant": 6, + "template": null, + "value": null + }, + { + "name": "VerifyCustomSignature", + "discriminant": 7, + "template": null, + "value": { + "ByIndex": 157 + } + }, + { + "name": "StoreSerializedString", + "discriminant": 8, + "template": null, + "value": { + "ByIndex": 158 + } + }, + { + "name": "DeserializeBytesAsString", + "discriminant": 9, + "template": null, + "value": null + }, + { + "name": "DeserializeCustomString", + "discriminant": 10, + "template": null, + "value": { + "ByIndex": 159 + } + }, + { + "name": "DeleteCells", + "discriminant": 11, + "template": null, + "value": { + "ByIndex": 160 + } + }, + { + "name": "UpdateAdmin", + "discriminant": 12, + "template": null, + "value": { + "ByIndex": 161 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "data_size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_WriteCustom", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "content", + "silent": false, + "value": { + "ByIndex": 152 + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "Immediate": "String" + } + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_ReadCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_HashBytes", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "filler", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u32", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_HashCustom", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "sign", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "message", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_VerifyCustomSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "sign", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "message", + "silent": false, + "value": { + "Immediate": "String" + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_StoreSerializedString", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_DeserializeCustomString", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "input", + "silent": false, + "value": { + "Immediate": { + "ByteVec": { + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_DeleteCells", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "begin", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "num_cells", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "new_admin", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 163 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "CallMessage", + "variants": [ + { + "name": "ReadAndSetManyIndividualValues", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 164 + } + }, + { + "name": "ReadAndSetHeavyState", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 165 + } + }, + { + "name": "RunCPUHeavyOperation", + "discriminant": 2, + "template": null, + "value": { + "ByIndex": 166 + } + } + ], + "hide_tag": false + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "number_of_operations", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "salt", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "number_of_new_values", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "max_heavy_state_size", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "salt", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "iterations", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Enum": { + "type_name": "UniquenessData", + "variants": [ + { + "name": "Nonce", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 168 + } + }, + { + "name": "Generation", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 169 + } + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "TxDetails", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "max_priority_fee_bips", + "silent": false, + "value": { + "ByIndex": 171 + }, + "doc": "" + }, + { + "display_name": "max_fee", + "silent": false, + "value": { + "ByIndex": 8 + }, + "doc": "" + }, + { + "display_name": "gas_limit", + "silent": false, + "value": { + "ByIndex": 66 + }, + "doc": "" + }, + { + "display_name": "chain_id", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "Immediate": { + "Integer": [ + "u64", + "Decimal" + ] + } + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 173 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "Version1", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "signatures", + "silent": false, + "value": { + "ByIndex": 174 + }, + "doc": "" + }, + { + "display_name": "unused_pub_keys", + "silent": false, + "value": { + "ByIndex": 176 + }, + "doc": "" + }, + { + "display_name": "min_signers", + "silent": false, + "value": { + "Immediate": { + "Integer": [ + "u8", + "Decimal" + ] + } + }, + "doc": "" + }, + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + }, + { + "display_name": "target_address", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "ByIndex": 175 + } + } + }, + { + "Struct": { + "type_name": "PubKeyAndSignature", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "signature", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 64, + "display": "Hex" + } + } + }, + "doc": "" + }, + { + "display_name": "pub_key", + "silent": false, + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + }, + "doc": "" + } + ] + } + }, + { + "Vec": { + "value": { + "Immediate": { + "ByteArray": { + "len": 32, + "display": "Hex" + } + } + } + } + }, + { + "Enum": { + "type_name": "UnsignedTransaction", + "variants": [ + { + "name": "V0", + "discriminant": 0, + "template": null, + "value": { + "ByIndex": 178 + } + }, + { + "name": "V1", + "discriminant": 1, + "template": null, + "value": { + "ByIndex": 180 + } + } + ], + "hide_tag": false + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 179 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "UnsignedTransactionV0", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + } + ] + } + }, + { + "Tuple": { + "template": null, + "peekable": false, + "fields": [ + { + "value": { + "ByIndex": 181 + }, + "silent": false, + "doc": "" + } + ] + } + }, + { + "Struct": { + "type_name": "UnsignedTransactionV1", + "template": null, + "peekable": false, + "fields": [ + { + "display_name": "runtime_call", + "silent": false, + "value": { + "ByIndex": 3 + }, + "doc": "" + }, + { + "display_name": "uniqueness", + "silent": false, + "value": { + "ByIndex": 167 + }, + "doc": "" + }, + { + "display_name": "details", + "silent": false, + "value": { + "ByIndex": 170 + }, + "doc": "" + }, + { + "display_name": "credential_address", + "silent": false, + "value": { + "ByIndex": 9 + }, + "doc": "" + }, + { + "display_name": "target_address", + "silent": false, + "value": { + "ByIndex": 25 + }, + "doc": "" + } + ] + } + } + ], + "root_type_indices": [ + 0, + 177, + 3, + 9 + ], + "chain_data": { + "chain_id": 4321, + "chain_name": "TestChain" + }, + "templates": [ + {}, + {}, + { + "transfer": { + "preencoded_bytes": [ + 0, + 1 + ], + "inputs": [ + [ + "to", + { + "type_link": { + "ByIndex": 9 + }, + "offset": 2 + } + ], + [ + "amount", + { + "type_link": { + "Immediate": { + "Integer": [ + "u128", + { + "FixedPoint": { + "FromSiblingField": { + "field_index": 1, + "byte_offset": 31 + } + } + } + ] + } + }, + "offset": 2 + } + ], + [ + "token_id", + { + "type_link": { + "ByIndex": 20 + }, + "offset": 2 + } + ] + ] + } + }, + {} + ], + "serde_metadata": [ + { + "name": "Transaction", + "fields_or_variants": [ + { + "name": "V0" + }, + { + "name": "V1" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "Version0", + "fields_or_variants": [ + { + "name": "signature" + }, + { + "name": "pub_key" + }, + { + "name": "runtime_call" + }, + { + "name": "uniqueness" + }, + { + "name": "details" + } + ] + }, + { + "name": "RuntimeCall", + "fields_or_variants": [ + { + "name": "bank" + }, + { + "name": "sequencer_registry" + }, + { + "name": "operator_incentives" + }, + { + "name": "attester_incentives" + }, + { + "name": "prover_incentives" + }, + { + "name": "accounts" + }, + { + "name": "uniqueness" + }, + { + "name": "chain_state" + }, + { + "name": "blob_storage" + }, + { + "name": "paymaster" + }, + { + "name": "revenue_share" + }, + { + "name": "mailbox" + }, + { + "name": "interchain_gas_paymaster" + }, + { + "name": "merkle_tree_hook" + }, + { + "name": "warp" + }, + { + "name": "evm" + }, + { + "name": "access_pattern" + }, + { + "name": "synthetic_load" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "create_token" + }, + { + "name": "transfer" + }, + { + "name": "burn" + }, + { + "name": "mint" + }, + { + "name": "freeze" + }, + { + "name": "update_admin" + }, + { + "name": "transfer_with_memo" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_CreateToken", + "fields_or_variants": [ + { + "name": "token_name" + }, + { + "name": "token_decimals" + }, + { + "name": "initial_balance" + }, + { + "name": "mint_to_address" + }, + { + "name": "admins" + }, + { + "name": "supply_cap" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "MultiAddressEvmSolana", + "fields_or_variants": [ + { + "name": "Standard" + }, + { + "name": "Evm" + }, + { + "name": "Solana" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Transfer", + "fields_or_variants": [ + { + "name": "to" + }, + { + "name": "coins" + } + ] + }, + { + "name": "Coins", + "fields_or_variants": [ + { + "name": "amount" + }, + { + "name": "token_id" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Burn", + "fields_or_variants": [ + { + "name": "coins" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Mint", + "fields_or_variants": [ + { + "name": "coins" + }, + { + "name": "mint_to_address" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Freeze", + "fields_or_variants": [ + { + "name": "token_id" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateAdmin", + "fields_or_variants": [ + { + "name": "new_admin" + }, + { + "name": "token_id" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_TransferWithMemo", + "fields_or_variants": [ + { + "name": "to" + }, + { + "name": "coins" + }, + { + "name": "memo" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "register" + }, + { + "name": "deposit" + }, + { + "name": "initiate_withdrawal" + }, + { + "name": "withdraw" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_Register", + "fields_or_variants": [ + { + "name": "da_address" + }, + { + "name": "amount" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Deposit", + "fields_or_variants": [ + { + "name": "da_address" + }, + { + "name": "amount" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_InitiateWithdrawal", "fields_or_variants": [ { - "name": "versioned_tx" + "name": "da_address" } ] }, { - "name": "VersionedTx", + "name": "__SovVirtualWallet_CallMessage_Withdraw", "fields_or_variants": [ { - "name": "V0" + "name": "da_address" } ] }, @@ -2760,82 +4895,309 @@ "fields_or_variants": [] }, { - "name": "Version0", + "name": "CallMessage", "fields_or_variants": [ { - "name": "signature" + "name": "update_reward_address" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateRewardAddress", + "fields_or_variants": [ + { + "name": "new_reward_address" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "register_attester" }, { - "name": "pub_key" + "name": "begin_exit_attester" }, { - "name": "runtime_call" + "name": "exit_attester" }, { - "name": "generation" + "name": "register_challenger" }, { - "name": "details" + "name": "exit_challenger" + }, + { + "name": "deposit_attester" } ] }, { - "name": "Ed25519Signature", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", "fields_or_variants": [ { - "name": "msg_sig" + "name": "register" + }, + { + "name": "deposit" + }, + { + "name": "exit" } ] }, { - "name": "Ed25519PublicKey", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", "fields_or_variants": [ { - "name": "pub_key" + "name": "insert_credential_id" + }, + { + "name": "add_credential_to_address" + }, + { + "name": "remove_credential_from_address" + }, + { + "name": "rotate_credential_on_address" } ] }, { - "name": "RuntimeCall", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_AddCredentialToAddress", "fields_or_variants": [ { - "name": "bank" + "name": "address" }, { - "name": "sequencer_registry" + "name": "credential" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RemoveCredentialFromAddress", + "fields_or_variants": [ + { + "name": "address" }, { - "name": "operator_incentives" + "name": "credential" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RotateCredentialOnAddress", + "fields_or_variants": [ + { + "name": "address" }, { - "name": "attester_incentives" + "name": "old_credential" }, { - "name": "prover_incentives" + "name": "new_credential" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "NotInstantiable", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "TerminateSetupMode" }, { - "name": "accounts" + "name": "SetOracleTime" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_SetOracleTime", + "fields_or_variants": [ + { + "name": "milliseconds_since_epoch" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "CallMessage", + "fields_or_variants": [ + { + "name": "register_paymaster" }, { - "name": "uniqueness" + "name": "set_payer_for_sequencer" }, { - "name": "chain_state" + "name": "update_policy" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", + "fields_or_variants": [ + { + "name": "policy" + } + ] + }, + { + "name": "PaymasterPolicyInitializer", + "fields_or_variants": [ + { + "name": "default_payee_policy" }, { - "name": "blob_storage" + "name": "payees" }, { - "name": "paymaster" + "name": "authorized_updaters" }, { - "name": "evm" + "name": "authorized_sequencers" + } + ] + }, + { + "name": "PayeePolicy", + "fields_or_variants": [ + { + "name": "allow" + }, + { + "name": "deny" + } + ] + }, + { + "name": "__SovVirtualWallet_PayeePolicy_Allow", + "fields_or_variants": [ + { + "name": "max_fee" + }, + { + "name": "gas_limit" + }, + { + "name": "max_gas_price" }, { - "name": "access_pattern" + "name": "transaction_limit" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "GasPrice", + "fields_or_variants": [ + { + "name": "value" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "AuthorizedSequencers", + "fields_or_variants": [ + { + "name": "all" }, { - "name": "synthetic_load" + "name": "some" } ] }, @@ -2844,45 +5206,48 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_SetPayerForSequencer", "fields_or_variants": [ { - "name": "create_token" - }, - { - "name": "transfer" - }, - { - "name": "burn" - }, + "name": "payer" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdatePolicy", + "fields_or_variants": [ { - "name": "mint" + "name": "payer" }, { - "name": "freeze" + "name": "update" } ] }, { - "name": "__SovVirtualWallet_CallMessage_CreateToken", + "name": "PolicyUpdate", "fields_or_variants": [ { - "name": "token_name" + "name": "sequencer_update" }, { - "name": "token_decimals" + "name": "updaters_to_add" }, { - "name": "initial_balance" + "name": "updaters_to_remove" }, { - "name": "mint_to_address" + "name": "payee_policies_to_set" }, { - "name": "admins" + "name": "payee_policies_to_delete" }, { - "name": "supply_cap" + "name": "default_policy" } ] }, @@ -2891,17 +5256,13 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "MultiAddress", + "name": "SequencerSetUpdate", "fields_or_variants": [ { - "name": "Standard" + "name": "allow_all" }, { - "name": "Vm" + "name": "update" } ] }, @@ -2909,6 +5270,17 @@ "name": "", "fields_or_variants": [] }, + { + "name": "AllowedSequencerUpdate", + "fields_or_variants": [ + { + "name": "to_add" + }, + { + "name": "to_remove" + } + ] + }, { "name": "", "fields_or_variants": [] @@ -2930,52 +5302,43 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_Transfer", + "name": "CallMessage", "fields_or_variants": [ { - "name": "to" + "name": "activate_revenue_share" }, { - "name": "coins" - } - ] - }, - { - "name": "Coins", - "fields_or_variants": [ + "name": "deactivate_revenue_share" + }, { - "name": "amount" + "name": "lower_revenue_percentage" }, { - "name": "token_id" + "name": "update_sovereign_admin" + }, + { + "name": "withdraw_rewards" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "__SovVirtualWallet_CallMessage_Burn", + "name": "__SovVirtualWallet_CallMessage_LowerRevenuePercentage", "fields_or_variants": [ { - "name": "coins" + "name": "percentage_in_basis_points" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Mint", + "name": "__SovVirtualWallet_CallMessage_UpdateSovereignAdmin", "fields_or_variants": [ { - "name": "coins" - }, - { - "name": "mint_to_address" + "name": "new_admin" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Freeze", + "name": "__SovVirtualWallet_CallMessage_WithdrawRewards", "fields_or_variants": [ { "name": "token_id" @@ -2990,27 +5353,36 @@ "name": "CallMessage", "fields_or_variants": [ { - "name": "register" - }, - { - "name": "deposit" + "name": "dispatch" }, { - "name": "initiate_withdrawal" + "name": "process" }, { - "name": "withdraw" + "name": "announce" } ] }, { - "name": "__SovVirtualWallet_CallMessage_Register", + "name": "__SovVirtualWallet_CallMessage_Dispatch", "fields_or_variants": [ { - "name": "da_address" + "name": "domain" }, { - "name": "amount" + "name": "recipient" + }, + { + "name": "body" + }, + { + "name": "metadata" + }, + { + "name": "relayer" + }, + { + "name": "gas_payment_limit" } ] }, @@ -3019,29 +5391,31 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_Deposit", + "name": "", + "fields_or_variants": [] + }, + { + "name": "__SovVirtualWallet_CallMessage_Process", "fields_or_variants": [ { - "name": "da_address" + "name": "metadata" }, { - "name": "amount" + "name": "message" } ] }, { - "name": "__SovVirtualWallet_CallMessage_InitiateWithdrawal", + "name": "__SovVirtualWallet_CallMessage_Announce", "fields_or_variants": [ { - "name": "da_address" - } - ] - }, - { - "name": "__SovVirtualWallet_CallMessage_Withdraw", - "fields_or_variants": [ + "name": "validator_address" + }, { - "name": "da_address" + "name": "storage_location" + }, + { + "name": "signature" } ] }, @@ -3050,20 +5424,8 @@ "fields_or_variants": [] }, { - "name": "CallMessage", - "fields_or_variants": [ - { - "name": "update_reward_address" - } - ] - }, - { - "name": "__SovVirtualWallet_CallMessage_UpdateRewardAddress", - "fields_or_variants": [ - { - "name": "new_reward_address" - } - ] + "name": "", + "fields_or_variants": [] }, { "name": "", @@ -3073,22 +5435,30 @@ "name": "CallMessage", "fields_or_variants": [ { - "name": "register_attester" + "name": "set_relayer_config" }, { - "name": "begin_exit_attester" + "name": "update_oracle_data" }, { - "name": "exit_attester" + "name": "claim_rewards" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_SetRelayerConfig", + "fields_or_variants": [ + { + "name": "domain_oracle_data" }, { - "name": "register_challenger" + "name": "domain_default_gas" }, { - "name": "exit_challenger" + "name": "default_gas" }, { - "name": "deposit_attester" + "name": "beneficiary" } ] }, @@ -3097,34 +5467,60 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] + "name": "DomainOracleData", + "fields_or_variants": [ + { + "name": "domain" + }, + { + "name": "data_value" + } + ] }, { - "name": "", - "fields_or_variants": [] + "name": "ExchangeRateAndGasPrice", + "fields_or_variants": [ + { + "name": "gas_price" + }, + { + "name": "token_exchange_rate" + } + ] }, { "name": "", "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "DomainDefaultGas", "fields_or_variants": [ { - "name": "register" + "name": "domain" }, { - "name": "deposit" + "name": "default_gas" + } + ] + }, + { + "name": "__SovVirtualWallet_CallMessage_UpdateOracleData", + "fields_or_variants": [ + { + "name": "domain" }, { - "name": "exit" + "name": "oracle_data" } ] }, { - "name": "", - "fields_or_variants": [] + "name": "__SovVirtualWallet_CallMessage_ClaimRewards", + "fields_or_variants": [ + { + "name": "relayer_address" + } + ] }, { "name": "", @@ -3138,136 +5534,134 @@ "name": "CallMessage", "fields_or_variants": [ { - "name": "insert_credential_id" + "name": "register" + }, + { + "name": "update" + }, + { + "name": "enroll_remote_router" + }, + { + "name": "un_enroll_remote_router" + }, + { + "name": "transfer_remote" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "NotInstantiable", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] + "name": "__SovVirtualWallet_CallMessage_Register", + "fields_or_variants": [ + { + "name": "admin" + }, + { + "name": "token_source" + }, + { + "name": "ism" + }, + { + "name": "remote_routers" + }, + { + "name": "inbound_transferrable_tokens_limit" + }, + { + "name": "inbound_limit_replenishment_per_slot" + }, + { + "name": "outbound_transferrable_tokens_limit" + }, + { + "name": "outbound_limit_replenishment_per_slot" + } + ] }, { - "name": "", - "fields_or_variants": [] + "name": "Admin", + "fields_or_variants": [ + { + "name": "None" + }, + { + "name": "InsecureOwner" + } + ] }, { "name": "", "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "TokenKind", "fields_or_variants": [ { - "name": "register_paymaster" + "name": "Synthetic" }, { - "name": "set_payer_for_sequencer" + "name": "Collateral" }, { - "name": "update_policy" - } - ] - }, - { - "name": "__SovVirtualWallet_CallMessage_RegisterPaymaster", - "fields_or_variants": [ - { - "name": "policy" + "name": "Native" } ] }, { - "name": "PaymasterPolicyInitializer", + "name": "__SovVirtualWallet_TokenKind_Synthetic", "fields_or_variants": [ { - "name": "default_payee_policy" - }, - { - "name": "payees" + "name": "remote_token_id" }, { - "name": "authorized_updaters" + "name": "remote_decimals" }, { - "name": "authorized_sequencers" + "name": "local_decimals" } ] }, { - "name": "PayeePolicy", + "name": "__SovVirtualWallet_TokenKind_Collateral", "fields_or_variants": [ { - "name": "allow" - }, - { - "name": "deny" + "name": "token" } ] }, { - "name": "__SovVirtualWallet_PayeePolicy_Allow", + "name": "Ism", "fields_or_variants": [ { - "name": "max_fee" - }, - { - "name": "gas_limit" + "name": "AlwaysTrust" }, { - "name": "max_gas_price" + "name": "TrustedRelayer" }, { - "name": "transaction_limit" + "name": "MessageIdMultisig" } ] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "", - "fields_or_variants": [] - }, - { - "name": "GasPrice", + "name": "__SovVirtualWallet_Ism_TrustedRelayer", "fields_or_variants": [ { - "name": "value" + "name": "relayer" } ] }, { - "name": "", - "fields_or_variants": [] + "name": "__SovVirtualWallet_Ism_MessageIdMultisig", + "fields_or_variants": [ + { + "name": "validators" + }, + { + "name": "threshold" + } + ] }, { "name": "", @@ -3282,13 +5676,28 @@ "fields_or_variants": [] }, { - "name": "AuthorizedSequencers", + "name": "__SovVirtualWallet_CallMessage_Update", "fields_or_variants": [ { - "name": "all" + "name": "warp_route" }, { - "name": "some" + "name": "admin" + }, + { + "name": "ism" + }, + { + "name": "inbound_transferrable_tokens_limit" + }, + { + "name": "inbound_limit_replenishment_per_slot" + }, + { + "name": "outbound_transferrable_tokens_limit" + }, + { + "name": "outbound_limit_replenishment_per_slot" } ] }, @@ -3301,44 +5710,50 @@ "fields_or_variants": [] }, { - "name": "__SovVirtualWallet_CallMessage_SetPayerForSequencer", + "name": "__SovVirtualWallet_CallMessage_EnrollRemoteRouter", "fields_or_variants": [ { - "name": "payer" + "name": "warp_route" + }, + { + "name": "remote_domain" + }, + { + "name": "remote_router_address" } ] }, { - "name": "__SovVirtualWallet_CallMessage_UpdatePolicy", + "name": "__SovVirtualWallet_CallMessage_UnEnrollRemoteRouter", "fields_or_variants": [ { - "name": "payer" + "name": "warp_route" }, { - "name": "update" + "name": "remote_domain" } ] }, { - "name": "PolicyUpdate", + "name": "__SovVirtualWallet_CallMessage_TransferRemote", "fields_or_variants": [ { - "name": "sequencer_update" + "name": "warp_route" }, { - "name": "updaters_to_add" + "name": "destination_domain" }, { - "name": "updaters_to_remove" + "name": "recipient" }, { - "name": "payee_policies_to_set" + "name": "amount" }, { - "name": "payee_policies_to_delete" + "name": "relayer" }, { - "name": "default_policy" + "name": "gas_payment_limit" } ] }, @@ -3347,13 +5762,13 @@ "fields_or_variants": [] }, { - "name": "SequencerSetUpdate", + "name": "CallMessage", "fields_or_variants": [ { - "name": "allow_all" + "name": "Call" }, { - "name": "update" + "name": "UpdateRuntimeConfig" } ] }, @@ -3362,13 +5777,10 @@ "fields_or_variants": [] }, { - "name": "AllowedSequencerUpdate", + "name": "RlpEvmTransaction", "fields_or_variants": [ { - "name": "to_add" - }, - { - "name": "to_remove" + "name": "rlp" } ] }, @@ -3377,8 +5789,21 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] + "name": "EvmRuntimeConfigUpdate", + "fields_or_variants": [ + { + "name": "new_hardfork" + }, + { + "name": "new_contract_creation_policy" + }, + { + "name": "chain_spec_update" + }, + { + "name": "new_admin" + } + ] }, { "name": "", @@ -3393,18 +5818,42 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "ContractCreationPolicyUpdate", "fields_or_variants": [ { - "name": "rlp" + "name": "Everyone" + }, + { + "name": "Allowlist" } ] }, { - "name": "RlpEvmTransaction", + "name": "__SovVirtualWallet_ContractCreationPolicyUpdate_Allowlist", "fields_or_variants": [ { - "name": "rlp" + "name": "add" + }, + { + "name": "remove" + } + ] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "ChainSpecUpdate", + "fields_or_variants": [ + { + "name": "new_limit_contract_code_size" + }, + { + "name": "new_block_gas_limit" + }, + { + "name": "new_tx_gas_limit" } ] }, @@ -3412,6 +5861,10 @@ "name": "", "fields_or_variants": [] }, + { + "name": "", + "fields_or_variants": [] + }, { "name": "AccessPatternMessages", "fields_or_variants": [ @@ -3451,9 +5904,6 @@ { "name": "delete_cells" }, - { - "name": "set_hook" - }, { "name": "update_admin" } @@ -3574,13 +6024,10 @@ ] }, { - "name": "__SovVirtualWallet_AccessPatternMessages_SetHook", + "name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", "fields_or_variants": [ { - "name": "pre" - }, - { - "name": "post" + "name": "new_admin" } ] }, @@ -3589,64 +6036,60 @@ "fields_or_variants": [] }, { - "name": "", - "fields_or_variants": [] - }, - { - "name": "HooksConfig", + "name": "CallMessage", "fields_or_variants": [ { - "name": "Read" + "name": "read_and_set_many_individual_values" }, { - "name": "Write" + "name": "read_and_set_heavy_state" }, { - "name": "Delete" + "name": "run_cpu_heavy_operation" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Read", + "name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", "fields_or_variants": [ { - "name": "begin" + "name": "number_of_operations" }, { - "name": "size" + "name": "salt" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Write", + "name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", "fields_or_variants": [ { - "name": "begin" + "name": "number_of_new_values" }, { - "name": "size" + "name": "max_heavy_state_size" }, { - "name": "data_size" + "name": "salt" } ] }, { - "name": "__SovVirtualWallet_HooksConfig_Delete", + "name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", "fields_or_variants": [ { - "name": "begin" - }, - { - "name": "size" + "name": "iterations" } ] }, { - "name": "__SovVirtualWallet_AccessPatternMessages_UpdateAdmin", + "name": "UniquenessData", "fields_or_variants": [ { - "name": "new_admin" + "name": "nonce" + }, + { + "name": "generation" } ] }, @@ -3655,66 +6098,105 @@ "fields_or_variants": [] }, { - "name": "CallMessage", + "name": "", + "fields_or_variants": [] + }, + { + "name": "TxDetails", "fields_or_variants": [ { - "name": "read_and_set_many_individual_values" + "name": "max_priority_fee_bips" }, { - "name": "read_and_set_heavy_state" + "name": "max_fee" }, { - "name": "run_cpu_heavy_operation" + "name": "gas_limit" + }, + { + "name": "chain_id" } ] }, { - "name": "__SovVirtualWallet_CallMessage_ReadAndSetManyIndividualValues", + "name": "", + "fields_or_variants": [] + }, + { + "name": "", + "fields_or_variants": [] + }, + { + "name": "Version1", "fields_or_variants": [ { - "name": "number_of_operations" + "name": "signatures" }, { - "name": "salt" + "name": "unused_pub_keys" + }, + { + "name": "min_signers" + }, + { + "name": "runtime_call" + }, + { + "name": "uniqueness" + }, + { + "name": "details" + }, + { + "name": "target_address" } ] }, { - "name": "__SovVirtualWallet_CallMessage_ReadAndSetHeavyState", + "name": "", + "fields_or_variants": [] + }, + { + "name": "PubKeyAndSignature", "fields_or_variants": [ { - "name": "number_of_new_values" - }, - { - "name": "max_heavy_state_size" + "name": "signature" }, { - "name": "salt" + "name": "pub_key" } ] }, { - "name": "__SovVirtualWallet_CallMessage_RunCPUHeavyOperation", + "name": "", + "fields_or_variants": [] + }, + { + "name": "UnsignedTransaction", "fields_or_variants": [ { - "name": "iterations" + "name": "V0" + }, + { + "name": "V1" } ] }, { - "name": "TxDetails", + "name": "", + "fields_or_variants": [] + }, + { + "name": "UnsignedTransactionV0", "fields_or_variants": [ { - "name": "max_priority_fee_bips" - }, - { - "name": "max_fee" + "name": "runtime_call" }, { - "name": "gas_limit" + "name": "uniqueness" }, { - "name": "chain_id" + "name": "details" } ] }, @@ -3723,18 +6205,24 @@ "fields_or_variants": [] }, { - "name": "UnsignedTransaction", + "name": "UnsignedTransactionV1", "fields_or_variants": [ { "name": "runtime_call" }, { - "name": "generation" + "name": "uniqueness" }, { "name": "details" + }, + { + "name": "credential_address" + }, + { + "name": "target_address" } ] } ] -} \ No newline at end of file +} diff --git a/typescript/packages/serializers/src/serializer.test.ts b/typescript/packages/serializers/src/serializer.test.ts index 11cfcecbdf..6752bff6aa 100644 --- a/typescript/packages/serializers/src/serializer.test.ts +++ b/typescript/packages/serializers/src/serializer.test.ts @@ -1,5 +1,88 @@ import { describe, expect, it } from "vitest"; -import { convertUint8ArraysToArrays } from "./serializer"; +import { Serializer, convertUint8ArraysToArrays } from "./serializer"; + +class TestSerializer extends Serializer { + public lastInput: unknown; + public lastIndex?: number; + + protected jsonToBorsh(input: unknown, index: number): Uint8Array { + this.lastInput = input; + this.lastIndex = index; + return new Uint8Array([1, 2, 3]); + } +} + +describe("Serializer", () => { + it("should wrap plain unsigned transactions as V0", () => { + const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); + const unsignedTx = { + runtime_call: { bank: "transfer" }, + uniqueness: { generation: 1 }, + details: { max_fee: "1000" }, + }; + + const result = serializer.serializeUnsignedTx(unsignedTx); + + expect(result).toEqual(new Uint8Array([1, 2, 3])); + expect(serializer.lastIndex).toBe(177); + expect(serializer.lastInput).toEqual({ V0: unsignedTx }); + }); + + it("should not double-wrap versioned unsigned transactions", () => { + const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); + const v0UnsignedTx = { + V0: { + runtime_call: { bank: "transfer" }, + uniqueness: { generation: 1 }, + details: { max_fee: "1000" }, + }, + }; + const v1UnsignedTx = { + V1: { + runtime_call: { bank: "transfer" }, + uniqueness: { nonce: 1 }, + details: { max_fee: "1000" }, + }, + }; + + serializer.serializeUnsignedTx(v0UnsignedTx); + expect(serializer.lastInput).toEqual(v0UnsignedTx); + + serializer.serializeUnsignedTx(v1UnsignedTx); + expect(serializer.lastInput).toEqual(v1UnsignedTx); + }); + + it("should convert Uint8Arrays when wrapping plain unsigned transactions", () => { + const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); + const unsignedTx = { + runtime_call: { + bank: { + transfer: { + to: new Uint8Array([1, 2, 3]), + }, + }, + }, + uniqueness: { generation: 1 }, + details: { max_fee: "1000" }, + }; + + serializer.serializeUnsignedTx(unsignedTx); + + expect(serializer.lastInput).toEqual({ + V0: { + runtime_call: { + bank: { + transfer: { + to: [1, 2, 3], + }, + }, + }, + uniqueness: { generation: 1 }, + details: { max_fee: "1000" }, + }, + }); + }); +}); describe("convertUint8ArraysToArrays", () => { it("should convert a simple Uint8Array to a regular array", () => { diff --git a/typescript/packages/serializers/src/serializer.ts b/typescript/packages/serializers/src/serializer.ts index e77d12bd90..25640fe260 100644 --- a/typescript/packages/serializers/src/serializer.ts +++ b/typescript/packages/serializers/src/serializer.ts @@ -81,8 +81,10 @@ export abstract class Serializer { * @returns The serialized Borsh bytes. */ serializeUnsignedTx(input: unknown): Uint8Array { + const unsignedTx = isVersionedUnsignedTx(input) ? input : { V0: input }; + return this.serialize( - input, + unsignedTx, this.lookupKnownTypeIndex(KnownTypeId.UnsignedTransaction), ); } @@ -139,3 +141,13 @@ export function convertUint8ArraysToArrays(obj: T): T { return obj; } + +function isVersionedUnsignedTx( + input: unknown, +): input is { V0?: unknown; V1?: unknown } { + return ( + typeof input === "object" && + input !== null && + ("V0" in input || "V1" in input) + ); +} diff --git a/typescript/packages/signers/src/wasm/eip712.test.ts b/typescript/packages/signers/src/wasm/eip712.test.ts index b2fce08bc9..61461eaa23 100644 --- a/typescript/packages/signers/src/wasm/eip712.test.ts +++ b/typescript/packages/signers/src/wasm/eip712.test.ts @@ -34,7 +34,7 @@ const sampleUnsignedTx = { }, }, }, - generation: "12345", + uniqueness: { generation: "12345" }, details: { max_priority_fee_bips: "1000", max_fee: "10000", @@ -50,7 +50,7 @@ const sampleUnsignedTx = { function createTestMessage(): Uint8Array { const unsignedTxBorsh = schema.jsonToBorsh( schema.knownTypeIndex(KnownTypeId.UnsignedTransaction), - JSON.stringify(sampleUnsignedTx), + JSON.stringify({ V0: sampleUnsignedTx }), ); return new Uint8Array([...unsignedTxBorsh, ...schema.chainHash]); } diff --git a/typescript/packages/universal-wallet-wasm/tests/schema.test.ts b/typescript/packages/universal-wallet-wasm/tests/schema.test.ts index ffc4f430cf..51368eed90 100644 --- a/typescript/packages/universal-wallet-wasm/tests/schema.test.ts +++ b/typescript/packages/universal-wallet-wasm/tests/schema.test.ts @@ -30,7 +30,7 @@ describe("Schema", () => { describe("chainHash", () => { it("should calculate the chain hash successfully", () => { const expected = - "672f49a623e325540b52fe25a255a584f4cdf8e2b0c5c1fca13eab8a92c610ed"; + "0747c78c9a62a856f5208d9a980136996209d96bfacc6aa9db8c5da2b8322792"; const actual = bytesToHex(schema.chainHash); expect(actual).toEqual(expected); @@ -39,7 +39,7 @@ describe("Schema", () => { describe("metadataHash", () => { it("should restore the metadata hash successfully", () => { const expected = - "57c66aa8f2935ec352980d39e4f48d6aa10faf322d96254c63ec64eed82eb5b3"; + "1b7964c74362ea00edd356995713768b068d69a0d13da3bf143959307a228e06"; const actual = bytesToHex(schema.metadataHash); expect(actual).toEqual(expected); @@ -137,13 +137,15 @@ describe("Schema", () => { }; const unsignedTransaction = { - runtime_call: call, - generation: "0", - details: { - max_priority_fee_bips: "1000", - max_fee: "10000", - gas_limit: null, - chain_id: "1", + V0: { + runtime_call: call, + uniqueness: { generation: "0" }, + details: { + max_priority_fee_bips: "1000", + max_fee: "10000", + gas_limit: null, + chain_id: "1", + }, }, }; @@ -168,7 +170,7 @@ describe("Schema", () => { expect(parsed.primaryType).toBe("UnsignedTransaction"); expect(JSON.stringify(parsed)).toEqual( - `{"domain":{"name":"TestChain","chainId":"0x10e1","salt":"0x672f49a623e325540b52fe25a255a584f4cdf8e2b0c5c1fca13eab8a92c610ed"},"types":{"Bank":[{"type":"Transfer","name":"Transfer"}],"Coins":[{"type":"uint128","name":"amount"},{"type":"string","name":"token_id"}],"EIP712Domain":[{"type":"string","name":"name"},{"type":"uint256","name":"chainId"},{"type":"bytes32","name":"salt"}],"MultiAddress":[{"type":"string","name":"Standard"}],"RuntimeCall":[{"type":"Bank","name":"Bank"}],"Transfer":[{"type":"MultiAddress","name":"to"},{"type":"Coins","name":"coins"}],"TxDetails":[{"type":"uint64","name":"max_priority_fee_bips"},{"type":"uint128","name":"max_fee"},{"type":"uint64","name":"chain_id"}],"UnsignedTransaction":[{"type":"RuntimeCall","name":"runtime_call"},{"type":"uint64","name":"generation"},{"type":"TxDetails","name":"details"}]},"primaryType":"UnsignedTransaction","message":{"details":{"chain_id":"1","max_fee":"10000","max_priority_fee_bips":"1000"},"generation":"0","runtime_call":{"Bank":{"Transfer":{"coins":{"amount":"1000","token_id":"token_1rwrh8gn2py0dl4vv65twgctmlwck6esm2as9dftumcw89kqqn3nqrduss6"},"to":{"Standard":"sov1lzkjgdaz08su3yevqu6ceywufl35se9f33kztu5cu2spja5hyyf"}}}}}}` + `{"domain":{"name":"TestChain","chainId":"0x10e1","salt":"0x0747c78c9a62a856f5208d9a980136996209d96bfacc6aa9db8c5da2b8322792"},"types":{"Bank":[{"type":"Transfer","name":"Transfer"}],"Coins":[{"type":"uint128","name":"amount"},{"type":"string","name":"token_id"}],"EIP712Domain":[{"type":"string","name":"name"},{"type":"uint256","name":"chainId"},{"type":"bytes32","name":"salt"}],"MultiAddressEvmSolana":[{"type":"string","name":"Standard"}],"RuntimeCall":[{"type":"Bank","name":"Bank"}],"Transfer":[{"type":"MultiAddressEvmSolana","name":"to"},{"type":"Coins","name":"coins"}],"TxDetails":[{"type":"uint64","name":"max_priority_fee_bips"},{"type":"uint128","name":"max_fee"},{"type":"uint64","name":"chain_id"}],"UniquenessData":[{"type":"uint64","name":"Generation"}],"UnsignedTransaction":[{"type":"V0","name":"V0"}],"V0":[{"type":"RuntimeCall","name":"runtime_call"},{"type":"UniquenessData","name":"uniqueness"},{"type":"TxDetails","name":"details"}]},"primaryType":"UnsignedTransaction","message":{"V0":{"details":{"chain_id":"1","max_fee":"10000","max_priority_fee_bips":"1000"},"runtime_call":{"Bank":{"Transfer":{"coins":{"amount":"1000","token_id":"token_1rwrh8gn2py0dl4vv65twgctmlwck6esm2as9dftumcw89kqqn3nqrduss6"},"to":{"Standard":"sov1lzkjgdaz08su3yevqu6ceywufl35se9f33kztu5cu2spja5hyyf"}}}},"uniqueness":{"Generation":"0"}}}}` ); }); }); @@ -192,13 +194,15 @@ describe("Schema", () => { }; const unsignedTransaction = { - runtime_call: call, - generation: "0", - details: { - max_priority_fee_bips: "1000", - max_fee: "10000", - gas_limit: null, - chain_id: "1", + V0: { + runtime_call: call, + uniqueness: { generation: "0" }, + details: { + max_priority_fee_bips: "1000", + max_fee: "10000", + gas_limit: null, + chain_id: "1", + }, }, }; @@ -215,7 +219,7 @@ describe("Schema", () => { // Should return a 32-byte hash expect(signingHash).toHaveLength(32); expect(bytesToHex(signingHash)).toEqual( - "4afeb093d3faef4587d0074eac770e5cbde89c2392b3d5b1ae3f3674d53a8cc4" + "e21ca89ff493401ac0371c8bc7dc5782c585c0c9de8a3583a4753cb203f85999" ); }); }); From e0e2e534cc958bc233085133fff3e879fb0a1908 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Fri, 8 May 2026 15:30:03 +0200 Subject: [PATCH 3/8] Simplify round 1 --- .../module-implementations/sov-accounts/src/call.rs | 8 ++++---- .../sov-accounts/src/capabilities.rs | 6 +----- crates/module-system/sov-address/src/evm/address.rs | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index bd4f53fb81..94f766ca17 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Context as _}; +use anyhow::{bail, Context as _}; use schemars::JsonSchema; use sov_modules_api::macros::{serialize, UniversalWallet}; use sov_modules_api::{Context, CredentialId, Spec, StateReader, TxState}; @@ -101,7 +101,7 @@ impl Accounts { anyhow::ensure!( self.is_authorized_for(&address, &credential, state) - .map_err(|err| anyhow!("Error raised while checking authorization: {err:?}"))?, + .context("Failed to check credential authorization")?, "CredentialId is not authorized for this address" ); @@ -122,7 +122,7 @@ impl Accounts { anyhow::ensure!( self.is_authorized_for(&address, &old_credential, state) - .map_err(|err| anyhow!("Error raised while checking authorization: {err:?}"))?, + .context("Failed to check credential authorization")?, "CredentialId is not authorized for this address" ); self.ensure_credential_not_authorized(&address, &new_credential, state)?; @@ -152,7 +152,7 @@ impl Accounts { if !self .enable_custom_account_mappings .get(state) - .map_err(|err| anyhow!("Error reading enable_custom_account_mappings: {err:?}"))? + .context("Failed to read enable_custom_account_mappings")? .expect( "`enable_custom_account_mappings` should not be None; it must be set at genesis.", ) diff --git a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs index 681f2c6fb5..1cdb03574a 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs @@ -52,10 +52,6 @@ impl Accounts { } let canonical_address: S::Address = (*credential_id).into(); - if canonical_address == *address { - return Ok(true); - } - - self.is_explicitly_authorized(address, credential_id, state) + Ok(canonical_address == *address) } } diff --git a/crates/module-system/sov-address/src/evm/address.rs b/crates/module-system/sov-address/src/evm/address.rs index 5a19743165..50adff0c95 100644 --- a/crates/module-system/sov-address/src/evm/address.rs +++ b/crates/module-system/sov-address/src/evm/address.rs @@ -167,9 +167,13 @@ mod tests { use borsh::{BorshDeserialize, BorshSerialize}; use sov_modules_api::configurable_spec::ConfigurableSpec; use sov_modules_api::execution_mode::Native; + use sov_modules_api::Spec; + use sov_test_utils::{MockDaSpec, MockZkvm}; use super::*; + type S = ConfigurableSpec; + #[test] fn credential_from_evm_address_roundtrips_to_vm_multi_address() { let eth_addr = @@ -189,10 +193,6 @@ mod tests { } } - use sov_modules_api::Spec; - use sov_test_utils::{MockDaSpec, MockZkvm}; - type S = ConfigurableSpec; - #[test] fn test_serde_json_multi_address_evm_vm() { let address = MultiAddressEvm::Vm( From d9bceedf6987e3400fd48e85ec71ede15a4e4eee Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Fri, 8 May 2026 16:19:09 +0200 Subject: [PATCH 4/8] Remove new `TryDecodeCredentialId` trait --- .../sov-address/src/evm/address.rs | 24 +++++-------------- crates/module-system/sov-address/src/lib.rs | 18 ++------------ .../stf/stf-declaration/src/address.rs | 10 +------- 3 files changed, 9 insertions(+), 43 deletions(-) diff --git a/crates/module-system/sov-address/src/evm/address.rs b/crates/module-system/sov-address/src/evm/address.rs index 50adff0c95..37248e00b3 100644 --- a/crates/module-system/sov-address/src/evm/address.rs +++ b/crates/module-system/sov-address/src/evm/address.rs @@ -1,5 +1,5 @@ use crate::evm::public_key::EthereumPublicKey; -use crate::{MultiAddress, Not28Bytes, TryDecodeCredentialId}; +use crate::{MultiAddress, Not28Bytes}; use alloy_primitives::{Address, AddressError}; use borsh::{BorshDeserialize, BorshSerialize}; use k256::elliptic_curve::sec1::ToEncodedPoint; @@ -145,21 +145,6 @@ pub type MultiAddressEvm = MultiAddress; impl BasicAddress for EthereumAddress {} impl Not28Bytes for EthereumAddress {} -impl TryDecodeCredentialId for EthereumAddress { - fn try_decode_credential_id(credential_id: CredentialId) -> Option { - // EVM credentials pack a 20-byte address into the low 20 bytes of - // the 32-byte credential (see `EthereumAddress::as_credential_id`). - // Credentials derived from native public-key hashes have essentially - // random bytes in that leading 12-byte slot. - let bytes: &[u8] = credential_id.0.as_ref(); - if bytes[..12] == [0u8; 12] { - Some(Self::from(credential_id)) - } else { - None - } - } -} - #[cfg(test)] mod tests { use std::str::FromStr; @@ -175,12 +160,15 @@ mod tests { type S = ConfigurableSpec; #[test] - fn credential_from_evm_address_roundtrips_to_vm_multi_address() { + fn credential_from_evm_address_stays_standard() { let eth_addr = EthereumAddress::from_str("0x71334bf1710D12c9f689cC819476fA589F08C64C").unwrap(); let cred = eth_addr.as_credential_id(); let back: MultiAddressEvm = cred.into(); - assert_eq!(back, MultiAddressEvm::Vm(eth_addr)); + assert_eq!( + back, + MultiAddressEvm::Standard(sov_modules_api::Address::from(cred)) + ); } #[test] diff --git a/crates/module-system/sov-address/src/lib.rs b/crates/module-system/sov-address/src/lib.rs index 5f57b60853..0dfc813124 100644 --- a/crates/module-system/sov-address/src/lib.rs +++ b/crates/module-system/sov-address/src/lib.rs @@ -46,12 +46,9 @@ impl From for MultiAddress } } -impl From for MultiAddress { +impl From for MultiAddress { fn from(value: CredentialId) -> Self { - match VmAddress::try_decode_credential_id(value) { - Some(vm) => Self::Vm(vm), - None => Self::Standard(Address::from(value)), - } + Self::Standard(Address::from(value)) } } @@ -179,14 +176,3 @@ pub trait Not28Bytes {} pub trait FromVmAddress { fn from_vm_address(value: VmAddress) -> Self; } - -/// Attempts to reconstruct a VM-specific address from a [`CredentialId`]. -/// Credentials that originate from a VM address carry the address bytes in a -/// recoverable encoding (for example EVM credentials pack the 20-byte address -/// into the low 20 bytes of the 32-byte credential); implementors check for -/// that shape and decode back. Returning `None` means this credential does -/// not encode an address in this VM's format and falls back to the standard -/// hash-derived [`Address`]. -pub trait TryDecodeCredentialId: Sized { - fn try_decode_credential_id(credential_id: CredentialId) -> Option; -} diff --git a/examples/demo-rollup/stf/stf-declaration/src/address.rs b/examples/demo-rollup/stf/stf-declaration/src/address.rs index 28116de86c..3b73b03474 100644 --- a/examples/demo-rollup/stf/stf-declaration/src/address.rs +++ b/examples/demo-rollup/stf/stf-declaration/src/address.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use schemars::JsonSchema; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sov_address::{EthereumAddress, FromVmAddress, TryDecodeCredentialId}; +use sov_address::{EthereumAddress, FromVmAddress}; use sov_hyperlane_integration::HyperlaneAddress; use sov_modules_api::macros::UniversalWallet; use sov_modules_api::{ @@ -54,14 +54,6 @@ impl From for MultiAddressEvmSolana { impl From for MultiAddressEvmSolana { fn from(value: CredentialId) -> Self { - // EVM credentials pack the 20-byte address into the low 20 bytes of - // the 32-byte credential (12 zero bytes of padding); recover the - // `Evm` variant so the canonical-address relationship round-trips - // for EVM signers. Native and Solana credentials are hashes with - // no such structural marker, so they fall back to `Standard`. - if let Some(eth_addr) = EthereumAddress::try_decode_credential_id(value) { - return Self::Evm(eth_addr); - } Self::Standard(Address::from(value)) } } From 08d48ee926e670e6a386a2fd0e9cc2b7708a0677 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 11 May 2026 12:31:56 +0200 Subject: [PATCH 5/8] Address PR feedback --- Cargo.toml | 1 + .../sov-accounts/src/call.rs | 5 +++-- .../sov-accounts/src/capabilities.rs | 9 ++++++--- .../sov-accounts/tests/integration/main.rs | 15 ++++++++++++--- examples/demo-rollup/README.md | 10 +++++----- examples/demo-rollup/README_CELESTIA.md | 2 +- .../tests/resync/data/mock_da.sqlite | Bin 49152 -> 49152 bytes 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 63c163171b..4a12ee8d22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ members = [ "crates/web3", "python/py_sovereign_web3/rust", ] +default-members = ["crates/rollup-interface", "crates/adapters/mock-da", "crates/adapters/mock-zkvm", "crates/full-node/sov-blob-sender", "crates/full-node/sov-db", "crates/full-node/sov-sequencer", "crates/full-node/sov-ledger-apis", "crates/full-node/sov-rollup-apis", "crates/full-node/sov-rollup-full-node-interface", "crates/full-node/sov-stf-runner", "crates/full-node/sov-metrics", "crates/full-node/sov-api-spec", "crates/full-node/full-node-configs", "crates/utils/sov-rest-utils", "crates/utils/nearly-linear", "crates/utils/sov-zkvm-utils", "crates/utils/sov-build", "crates/module-system/hyperlane", "crates/module-system/sov-cli", "crates/module-system/sov-modules-stf-blueprint", "crates/module-system/sov-modules-rollup-blueprint", "crates/module-system/sov-modules-macros", "crates/module-system/sov-kernels", "crates/module-system/sov-state", "crates/module-system/sov-modules-api", "crates/module-system/sov-address", "crates/utils/sov-test-utils", "crates/utils/sov-evm-test-utils", "crates/module-system/module-implementations/sov-accounts", "crates/module-system/module-implementations/sov-bank", "crates/module-system/module-implementations/sov-chain-state", "crates/module-system/module-implementations/sov-blob-storage", "crates/module-system/module-implementations/sov-evm", "crates/module-system/module-implementations/sov-paymaster", "crates/module-system/module-implementations/sov-prover-incentives", "crates/module-system/module-implementations/sov-attester-incentives", "crates/module-system/module-implementations/sov-sequencer-registry", "crates/module-system/module-implementations/sov-value-setter", "crates/module-system/module-implementations/sov-revenue-share", "crates/module-system/module-implementations/sov-synthetic-load", "crates/module-system/module-implementations/module-template", "crates/module-system/module-implementations/integration-tests", "crates/module-system/sov-capabilities", "crates/module-system/module-implementations/sov-uniqueness", "crates/module-system/module-implementations/sov-timelock", "crates/utils/sov-node-client", "crates/universal-wallet/schema", "crates/universal-wallet/macros", "crates/universal-wallet/macro-helpers", "crates/utils/workspace-hack", "crates/web3", "python/py_sovereign_web3/rust", "crates/module-system/module-implementations/extern/hyperlane-solana-register"] [workspace.package] version = "0.3.0" edition = "2021" diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index 94f766ca17..2ecdfdb825 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -166,9 +166,10 @@ impl Accounts { /// authorization path has already verified the caller controls a /// credential authorized for `context.sender()`. fn ensure_caller_owns(&self, address: &S::Address, context: &Context) -> anyhow::Result<()> { + let sender = context.sender(); anyhow::ensure!( - context.sender() == address, - "Caller is not authorized to modify credentials for this address" + sender == address, + "Caller {sender} is not authorized to modify credentials for address {address}" ); Ok(()) } diff --git a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs index 1cdb03574a..75dc12d6aa 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs @@ -35,9 +35,12 @@ impl Accounts { /// Returns `true` if `credential_id` is authorized to act as `address`. /// - /// Returns `true` when `address` is the canonical address of - /// `credential_id` (i.e. `credential_id.into() == address`) or when an - /// explicit `account_owners` authorization exists. + /// Lookup order: + /// 1. If `account_owners[(address, credential_id)]` has an explicit + /// entry, that value is authoritative — including `false`, which + /// is how `revoke_credential` denies the canonical fallback. + /// 2. Otherwise, returns `true` iff `address` is the canonical address + /// of `credential_id` (i.e. `credential_id.into() == address`). pub fn is_authorized_for>( &self, address: &S::Address, diff --git a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs index 6b642113ed..c9897437c1 100644 --- a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs +++ b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs @@ -1072,6 +1072,7 @@ fn test_add_credential_to_address_non_owner_rejected() { let attacker = TestUser::::generate_with_default_balance(); let victim = TestUser::::generate_with_default_balance(); let victim_address = victim.address(); + let attacker_address = attacker.address(); let genesis_config = HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![attacker.clone(), victim]); @@ -1093,7 +1094,9 @@ fn test_add_credential_to_address_non_owner_rejected() { TxEffect::Reverted(contents) => { assert_eq!( contents.reason.to_string(), - "Caller is not authorized to modify credentials for this address" + format!( + "Caller {attacker_address} is not authorized to modify credentials for address {victim_address}" + ) ); } other => panic!("Expected reverted transaction, got {other:?}"), @@ -1235,6 +1238,7 @@ fn test_remove_credential_non_owner_rejected() { let victim = TestUser::::generate_with_default_balance(); let victim_address = victim.address(); let victim_credential = victim.credential_id(); + let attacker_address = attacker.address(); // Seed victim's `(address, credential)` so the attacker's revoke attempt // targets a real tuple. Otherwise the handler would fail on the tuple @@ -1261,7 +1265,9 @@ fn test_remove_credential_non_owner_rejected() { TxEffect::Reverted(contents) => { assert_eq!( contents.reason.to_string(), - "Caller is not authorized to modify credentials for this address" + format!( + "Caller {attacker_address} is not authorized to modify credentials for address {victim_address}" + ) ); } other => panic!("Expected reverted transaction, got {other:?}"), @@ -1728,6 +1734,7 @@ fn test_rotate_credential_non_owner_rejected() { let victim = TestUser::::generate_with_default_balance(); let victim_address = victim.address(); let victim_credential = victim.credential_id(); + let attacker_address = attacker.address(); let genesis_config = HighLevelOptimisticGenesisConfig::generate().add_accounts(vec![attacker.clone(), victim]); @@ -1754,7 +1761,9 @@ fn test_rotate_credential_non_owner_rejected() { TxEffect::Reverted(contents) => { assert_eq!( contents.reason.to_string(), - "Caller is not authorized to modify credentials for this address" + format!( + "Caller {attacker_address} is not authorized to modify credentials for address {victim_address}" + ) ); } other => panic!("Expected reverted transaction, got {other:?}"), diff --git a/examples/demo-rollup/README.md b/examples/demo-rollup/README.md index 07ef8d7ed7..c0cc33fd13 100644 --- a/examples/demo-rollup/README.md +++ b/examples/demo-rollup/README.md @@ -109,9 +109,9 @@ Once a batch is submitted, the output should also contain the transaction hashes ```text 2025-10-24T12:40:48.335845Z INFO sov_cli::workflows::node: Executing node workflow -2025-10-24T12:40:48.348358Z INFO sov_cli::workflows::node: Submitting tx index=0 tx_hash=0x14e580a7cf540660416c54f6926b8fdcc23375d8e63ade3b8ab433fe5fc233e4 +2025-10-24T12:40:48.348358Z INFO sov_cli::workflows::node: Submitting tx index=0 tx_hash=0x9994bb97c4567683341bf354fa531b750e7a2f071879a1729d462097ac5f7d90 2025-10-24T12:40:48.348379Z INFO sov_node_client: Calling `publish_batch` sequencer endpoint txs_included=1 -2025-10-24T12:40:48.358028Z INFO sov_node_client: Submitted tx hash="0x14e580a7cf540660416c54f6926b8fdcc23375d8e63ade3b8ab433fe5fc233e4" +2025-10-24T12:40:48.358028Z INFO sov_node_client: Submitted tx hash="0x9994bb97c4567683341bf354fa531b750e7a2f071879a1729d462097ac5f7d90" 2025-10-24T12:40:48.358060Z INFO sov_node_client: Going to wait for batch to be processed max_waiting_time=300s 2025-10-24T12:40:50.477229Z INFO sov_node_client: Rollup has processed the submitted batch! ``` @@ -121,7 +121,7 @@ this case have the TokenCreated Event ```sh,test-ci,bashtestmd:compare-output $ sleep 5 -$ curl -sS http://127.0.0.1:12346/ledger/txs/0x14e580a7cf540660416c54f6926b8fdcc23375d8e63ade3b8ab433fe5fc233e4/events | jq +$ curl -sS http://127.0.0.1:12346/ledger/txs/0x9994bb97c4567683341bf354fa531b750e7a2f071879a1729d462097ac5f7d90/events | jq [ { "type": "event", @@ -155,7 +155,7 @@ $ curl -sS http://127.0.0.1:12346/ledger/txs/0x14e580a7cf540660416c54f6926b8fdcc "type": "moduleRef", "name": "Bank" }, - "tx_hash": "0x14e580a7cf540660416c54f6926b8fdcc23375d8e63ade3b8ab433fe5fc233e4" + "tx_hash": "0x9994bb97c4567683341bf354fa531b750e7a2f071879a1729d462097ac5f7d90" } ] ``` @@ -334,7 +334,7 @@ Adding the following transaction to batch: } } }, - "chain_hash": "0xb089bb60c437cf497dcad9309d4f21fded70c2a585c27117a25b42b2f852b846", + "chain_hash": "0xde48058585f3408f8ad65e2b09c62e1f00fec71e1debd88ce23c40eb7e25b2c8", "details": { "max_priority_fee_bips": 0, "max_fee": "100000000", diff --git a/examples/demo-rollup/README_CELESTIA.md b/examples/demo-rollup/README_CELESTIA.md index d3acf5d51e..ddeb6e7498 100644 --- a/examples/demo-rollup/README_CELESTIA.md +++ b/examples/demo-rollup/README_CELESTIA.md @@ -290,7 +290,7 @@ Adding the following transaction to batch: } } }, - "chain_hash": "0xb089bb60c437cf497dcad9309d4f21fded70c2a585c27117a25b42b2f852b846", + "chain_hash": "0xde48058585f3408f8ad65e2b09c62e1f00fec71e1debd88ce23c40eb7e25b2c8", "details": { "max_priority_fee_bips": 0, "max_fee": "100000000", diff --git a/examples/demo-rollup/tests/resync/data/mock_da.sqlite b/examples/demo-rollup/tests/resync/data/mock_da.sqlite index ecfeb1b7571c141d2764feafd6123829f56160d4..77f64b994dc2a5540980a8cb85eb09a0c556a74b 100644 GIT binary patch literal 49152 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

ro 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 E1v#hyJ^%m! From 4bc01f3a95b35bd315c6704656b2a182c07297e3 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 11 May 2026 12:20:46 +0200 Subject: [PATCH 6/8] capabilities: trust authenticator default address on None override Description When `address_override` is `None`, `resolve_authorized_sender()` was re-checking `auth_data.default_address` through `Accounts::is_authorized_for()`. That works when the authenticator's default address matches `credential_id.into()`, but it breaks authenticators whose default address uses a different address form. For `sov-evm` with `MultiAddressEvm`, the authenticator sets `default_address` to `Vm(eth_addr)`, while the canonical fallback in `is_authorized_for()` is `Standard(credential_id.into())`. Ordinary EVM transactions were therefore skipped with "not authorized for resolved address" unless an explicit `(vm_address, credential_id)` mapping had already been inserted. Fix the shared `address_override = None` path to trust the authenticator-selected default address unless that exact `(address, credential_id)` pair is explicitly denied in `account_owners`. Keep `Some(address_override)` strict, and preserve revocation semantics for explicit `false` entries. This restores the default EVM sender path without changing EVM auth semantics or requiring pre-populated account mappings. --- .../sov-accounts/src/call.rs | 4 +- .../sov-accounts/src/capabilities.rs | 21 ++++++ .../sov-accounts/tests/integration/main.rs | 2 +- .../sov-address/src/evm/address.rs | 8 +- .../module-system/sov-capabilities/src/lib.rs | 75 ++++++++----------- 5 files changed, 60 insertions(+), 50 deletions(-) diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index 2ecdfdb825..ccaf1e515a 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -122,8 +122,8 @@ impl Accounts { anyhow::ensure!( self.is_authorized_for(&address, &old_credential, state) - .context("Failed to check credential authorization")?, - "CredentialId is not authorized for this address" + .context("Failed to check old credential authorization")?, + "old_credential is not authorized for this address" ); self.ensure_credential_not_authorized(&address, &new_credential, state)?; diff --git a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs index 75dc12d6aa..358cc1b793 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/capabilities.rs @@ -33,6 +33,27 @@ impl Accounts { .unwrap_or(false)) } + /// Returns `true` if the authenticator-selected default address may be used + /// with `credential_id`. + /// + /// Lookup order: + /// 1. If `account_owners[(address, credential_id)]` has an explicit + /// entry, that value is authoritative — including `false`, which + /// is how `revoke_credential` denies the default path. + /// 2. Otherwise, returns `true`: the authenticator's declared default + /// address is trusted for the `address_override = None` path. + pub fn is_default_address_authorized>( + &self, + address: &S::Address, + credential_id: &CredentialId, + state: &mut ST, + ) -> Result { + Ok(self + .account_owners + .get(&AccountOwnerKey::new(*address, *credential_id), state)? + .unwrap_or(true)) + } + /// Returns `true` if `credential_id` is authorized to act as `address`. /// /// Lookup order: diff --git a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs index c9897437c1..62be3f0bec 100644 --- a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs +++ b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs @@ -1654,7 +1654,7 @@ fn test_rotate_credential_old_not_authorized_rejected() { TxEffect::Reverted(contents) => { assert_eq!( contents.reason.to_string(), - "CredentialId is not authorized for this address" + "old_credential is not authorized for this address" ); } other => panic!("Expected reverted transaction, got {other:?}"), diff --git a/crates/module-system/sov-address/src/evm/address.rs b/crates/module-system/sov-address/src/evm/address.rs index 37248e00b3..24f76ba67b 100644 --- a/crates/module-system/sov-address/src/evm/address.rs +++ b/crates/module-system/sov-address/src/evm/address.rs @@ -175,10 +175,10 @@ mod tests { fn credential_from_native_hash_stays_standard() { let cred = CredentialId::from_bytes([0x42; 32]); let back: MultiAddressEvm = cred.into(); - match back { - MultiAddressEvm::Standard(_) => {} - MultiAddressEvm::Vm(_) => panic!("non-zero-prefix credential must not decode to Vm"), - } + assert_eq!( + back, + MultiAddressEvm::Standard(sov_modules_api::Address::from(cred)) + ); } #[test] diff --git a/crates/module-system/sov-capabilities/src/lib.rs b/crates/module-system/sov-capabilities/src/lib.rs index 8e55edfe74..0e9f4f4261 100644 --- a/crates/module-system/sov-capabilities/src/lib.rs +++ b/crates/module-system/sov-capabilities/src/lib.rs @@ -314,33 +314,7 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' execution_context: ExecutionContext, sequencer_type: SequencerType, ) -> anyhow::Result> { - // `Some` requires an explicit `(addr, cred)` entry — overriding the default - // is opt-in. `None` allows the canonical-address fallback but still rejects - // when an explicit `false` (e.g. from `revoke_credential`) is recorded. - let sender = match auth_data.address_override { - Some(address_override) => { - anyhow::ensure!( - self.accounts.is_explicitly_authorized( - &address_override, - &auth_data.credential_id, - state, - )?, - "not authorized for address override" - ); - address_override - } - None => { - anyhow::ensure!( - self.accounts.is_authorized_for( - &auth_data.default_address, - &auth_data.credential_id, - state, - )?, - "not authorized for resolved address" - ); - auth_data.default_address - } - }; + let sender = self.resolve_authorized_sender(auth_data, state)?; Ok(Context::new( sender, @@ -360,8 +334,33 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' state: &mut impl StateAccessor, execution_context: ExecutionContext, ) -> anyhow::Result> { - // The tx sender & sequencer are the same entity on this path. - let address = match auth_data.address_override { + // On the unregistered path the sender pays its own sequencing, so the + // resolved address doubles as `sequencer_rollup_address`. + let address = self.resolve_authorized_sender(auth_data, state)?; + + Ok(Context::new( + address, + auth_data.credentials.clone(), + address, + *sequencer, + None, + execution_context, + SequencerType::NonPreferred, + )) + } +} + +impl StandardProvenRollupCapabilities<'_, S, T> { + /// Picks the sender address for a transaction: + /// - `address_override = Some(_)` requires an explicit `(addr, cred)` entry — overriding the default is opt-in. + /// - `address_override = None` uses the authenticator-selected default address unless that + /// exact `(addr, cred)` pair is explicitly denied (e.g. by `revoke_credential`). + fn resolve_authorized_sender( + &mut self, + auth_data: &AuthorizationData, + state: &mut impl StateAccessor, + ) -> anyhow::Result { + match auth_data.address_override { Some(address_override) => { anyhow::ensure!( self.accounts.is_explicitly_authorized( @@ -371,30 +370,20 @@ impl TransactionAuthorizer for StandardProvenRollupCapabilities<' )?, "not authorized for address override" ); - address_override + Ok(address_override) } None => { anyhow::ensure!( - self.accounts.is_authorized_for( + self.accounts.is_default_address_authorized( &auth_data.default_address, &auth_data.credential_id, state, )?, "not authorized for resolved address" ); - auth_data.default_address + Ok(auth_data.default_address) } - }; - - Ok(Context::new( - address, - auth_data.credentials.clone(), - address, - *sequencer, - None, - execution_context, - SequencerType::NonPreferred, - )) + } } } From cefae1ff4d49445985a619f3bfd7e6e698cd728f Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 11 May 2026 12:37:19 +0200 Subject: [PATCH 7/8] More fixes --- .../sov-accounts/src/call.rs | 4 +- .../sov-accounts/tests/integration/main.rs | 133 ++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/crates/module-system/module-implementations/sov-accounts/src/call.rs b/crates/module-system/module-implementations/sov-accounts/src/call.rs index ccaf1e515a..cbff0788ef 100644 --- a/crates/module-system/module-implementations/sov-accounts/src/call.rs +++ b/crates/module-system/module-implementations/sov-accounts/src/call.rs @@ -182,8 +182,8 @@ impl Accounts { ) -> anyhow::Result<()> { anyhow::ensure!( !self - .is_explicitly_authorized(address, credential, state) - .context("Failed to read account owner")?, + .is_authorized_for(address, credential, state) + .context("Failed to check credential authorization")?, "CredentialId already authorized for this address" ); Ok(()) diff --git a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs index 62be3f0bec..a8cfdefde2 100644 --- a/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs +++ b/crates/module-system/module-implementations/sov-accounts/tests/integration/main.rs @@ -157,6 +157,44 @@ fn test_insert_existing_credential_fails() { }); } +/// A credential already authorized canonically for the sender's address cannot +/// be inserted through `InsertCredentialId`. +#[test] +fn test_insert_canonical_credential_rejected() { + let ( + TestData { + non_registered_account: sender, + .. + }, + mut runner, + ) = setup(); + let canonical_credential = sender.credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: sender.create_plain_message::>(CallMessage::InsertCredentialId( + canonical_credential, + )), + assert: Box::new(move |result, state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId already authorized for this address" + ); + let accounts = Accounts::::default(); + assert!( + !accounts + .is_explicitly_authorized(&sender.address(), &canonical_credential, state) + .unwrap(), + "canonical duplicate rejection must not materialize an explicit entry" + ); + } + other => { + panic!("Expected reverted transaction for canonical credential, got {other:?}") + } + }), + }); +} + /// Tests the multisig functionality of the Accounts module. /// /// Seeds genesis with a `TestUser` whose custom `credential_id` matches the @@ -1147,6 +1185,44 @@ fn test_add_credential_duplicate_rejected() { }); } +/// Adding a credential that is already authorized canonically for the owner's +/// address fails cleanly. +#[test] +fn test_add_credential_canonical_duplicate_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let canonical_credential = owner.credential_id(); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>(CallMessage::AddCredentialToAddress { + address: owner_address, + credential: canonical_credential, + }), + assert: Box::new(move |result, state| match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId already authorized for this address" + ); + let accounts = Accounts::::default(); + assert!( + !accounts + .is_explicitly_authorized(&owner_address, &canonical_credential, state) + .unwrap(), + "canonical duplicate rejection must not materialize an explicit entry" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + }), + }); +} + /// The owner can revoke a credential from their own address. #[test] fn test_remove_credential_from_address_by_owner() { @@ -1727,6 +1803,63 @@ fn test_rotate_credential_new_already_authorized_rejected() { }); } +/// Attempting to rotate in the owner's canonical credential fails without +/// revoking the old credential or materializing a duplicate explicit entry. +#[test] +fn test_rotate_credential_new_canonical_already_authorized_rejected() { + let ( + TestData { + non_registered_account: owner, + .. + }, + mut runner, + ) = setup(); + let owner_address = owner.address(); + let old_credential = TestPrivateKey::generate().pub_key().credential_id(); + let canonical_credential = owner.credential_id(); + + runner.execute(owner.create_plain_message::>( + CallMessage::AddCredentialToAddress { + address: owner_address, + credential: old_credential, + }, + )); + + runner.execute_transaction(TransactionTestCase { + input: owner.create_plain_message::>( + CallMessage::RotateCredentialOnAddress { + address: owner_address, + old_credential, + new_credential: canonical_credential, + }, + ), + assert: Box::new(move |result, state| { + match result.tx_receipt { + TxEffect::Reverted(contents) => { + assert_eq!( + contents.reason.to_string(), + "CredentialId already authorized for this address" + ); + } + other => panic!("Expected reverted transaction, got {other:?}"), + } + let accounts = Accounts::::default(); + assert!(accounts + .is_authorized_for(&owner_address, &old_credential, state) + .unwrap()); + assert!(accounts + .is_authorized_for(&owner_address, &canonical_credential, state) + .unwrap()); + assert!( + !accounts + .is_explicitly_authorized(&owner_address, &canonical_credential, state) + .unwrap(), + "canonical duplicate rejection must not materialize an explicit entry" + ); + }), + }); +} + /// A caller cannot rotate credentials on an address they don't own. #[test] fn test_rotate_credential_non_owner_rejected() { From 756b15468e510f67714cd868976094ffc99c3a06 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Tue, 12 May 2026 10:46:18 +0200 Subject: [PATCH 8/8] Fixing typescript feedback --- .../serializers/src/serializer.test.ts | 33 ++++++------------- .../packages/serializers/src/serializer.ts | 14 +------- .../packages/web3/src/rollup/rollup.test.ts | 6 ++-- typescript/packages/web3/src/rollup/rollup.ts | 4 ++- 4 files changed, 17 insertions(+), 40 deletions(-) diff --git a/typescript/packages/serializers/src/serializer.test.ts b/typescript/packages/serializers/src/serializer.test.ts index 6752bff6aa..b6c0c72d0a 100644 --- a/typescript/packages/serializers/src/serializer.test.ts +++ b/typescript/packages/serializers/src/serializer.test.ts @@ -13,22 +13,7 @@ class TestSerializer extends Serializer { } describe("Serializer", () => { - it("should wrap plain unsigned transactions as V0", () => { - const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); - const unsignedTx = { - runtime_call: { bank: "transfer" }, - uniqueness: { generation: 1 }, - details: { max_fee: "1000" }, - }; - - const result = serializer.serializeUnsignedTx(unsignedTx); - - expect(result).toEqual(new Uint8Array([1, 2, 3])); - expect(serializer.lastIndex).toBe(177); - expect(serializer.lastInput).toEqual({ V0: unsignedTx }); - }); - - it("should not double-wrap versioned unsigned transactions", () => { + it("should pass versioned unsigned transactions through unchanged", () => { const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); const v0UnsignedTx = { V0: { @@ -52,18 +37,20 @@ describe("Serializer", () => { expect(serializer.lastInput).toEqual(v1UnsignedTx); }); - it("should convert Uint8Arrays when wrapping plain unsigned transactions", () => { + it("should convert Uint8Arrays nested in unsigned transactions", () => { const serializer = new TestSerializer({ root_type_indices: [0, 177, 3] }); const unsignedTx = { - runtime_call: { - bank: { - transfer: { - to: new Uint8Array([1, 2, 3]), + V0: { + runtime_call: { + bank: { + transfer: { + to: new Uint8Array([1, 2, 3]), + }, }, }, + uniqueness: { generation: 1 }, + details: { max_fee: "1000" }, }, - uniqueness: { generation: 1 }, - details: { max_fee: "1000" }, }; serializer.serializeUnsignedTx(unsignedTx); diff --git a/typescript/packages/serializers/src/serializer.ts b/typescript/packages/serializers/src/serializer.ts index 25640fe260..e77d12bd90 100644 --- a/typescript/packages/serializers/src/serializer.ts +++ b/typescript/packages/serializers/src/serializer.ts @@ -81,10 +81,8 @@ export abstract class Serializer { * @returns The serialized Borsh bytes. */ serializeUnsignedTx(input: unknown): Uint8Array { - const unsignedTx = isVersionedUnsignedTx(input) ? input : { V0: input }; - return this.serialize( - unsignedTx, + input, this.lookupKnownTypeIndex(KnownTypeId.UnsignedTransaction), ); } @@ -141,13 +139,3 @@ export function convertUint8ArraysToArrays(obj: T): T { return obj; } - -function isVersionedUnsignedTx( - input: unknown, -): input is { V0?: unknown; V1?: unknown } { - return ( - typeof input === "object" && - input !== null && - ("V0" in input || "V1" in input) - ); -} diff --git a/typescript/packages/web3/src/rollup/rollup.test.ts b/typescript/packages/web3/src/rollup/rollup.test.ts index 4dff76d6ca..ccaec033eb 100644 --- a/typescript/packages/web3/src/rollup/rollup.test.ts +++ b/typescript/packages/web3/src/rollup/rollup.test.ts @@ -252,9 +252,9 @@ describe("Rollup", () => { expect(mockSigner.sign).toHaveBeenCalledWith( new Uint8Array([7, 8, 9, 1, 2, 3, 4]), ); - expect(mockSerializer.serializeUnsignedTx).toHaveBeenCalledWith( - unsignedTx, - ); + expect(mockSerializer.serializeUnsignedTx).toHaveBeenCalledWith({ + V0: unsignedTx, + }); }); it("should pass options to submitTransaction", async () => { diff --git a/typescript/packages/web3/src/rollup/rollup.ts b/typescript/packages/web3/src/rollup/rollup.ts index 72b4dc1dca..e8056a1034 100644 --- a/typescript/packages/web3/src/rollup/rollup.ts +++ b/typescript/packages/web3/src/rollup/rollup.ts @@ -282,7 +282,9 @@ export class Rollup { signer: Signer, ): Promise { const serializer = await this.serializer(); - const serializedUnsignedTx = serializer.serializeUnsignedTx(unsignedTx); + const serializedUnsignedTx = serializer.serializeUnsignedTx({ + V0: unsignedTx, + }); const chainHash = await this.chainHash(); const signature = await signer.sign( new Uint8Array([...serializedUnsignedTx, ...chainHash]),