Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
688f2fd
feat(consensus): activate native multisig accounts
joshieDo Aug 19, 2026
adc8614
fix(consensus): bind multisig authorization to canonical owners
joshieDo Aug 19, 2026
cef40ba
test(node): fund fill transaction signers
joshieDo Aug 19, 2026
49cddec
test(consensus): update T11 gas snapshots
joshieDo Aug 19, 2026
768713f
fix(consensus): charge nested multisig account validation
joshieDo Aug 19, 2026
d1dc907
fix(consensus): check multisig fees before quorum
joshieDo Aug 19, 2026
d296715
test(consensus): separate intrinsic nested gas
joshieDo Aug 19, 2026
2fa5cda
fix(consensus): enforce multisig validation invariants
joshieDo Aug 19, 2026
bca979b
fix(consensus): select companion bootstrap config
joshieDo Aug 19, 2026
234ff4d
test(consensus): clean companion bootstrap setup
joshieDo Aug 19, 2026
4168877
refactor(consensus): clarify multisig validation flow
joshieDo Aug 19, 2026
8f46d93
refactor(consensus): use registered multisig configs
joshieDo Aug 19, 2026
c2097d3
refactor(consensus): use direct multisig config loader
joshieDo Aug 19, 2026
bbfe1c6
fix(consensus): harden multisig validation boundaries
joshieDo Aug 20, 2026
4b13003
perf(consensus): gate multisig registry reads by signature shape
joshieDo Aug 20, 2026
aec67db
test(consensus): remove unreachable keychain owner case
joshieDo Aug 20, 2026
01da56d
refactor(consensus): remove dead multisig config load
joshieDo Aug 20, 2026
45d1c38
test(consensus): align multisig rejection fixtures
joshieDo Aug 20, 2026
365c7f4
test(consensus): match typed multisig signature errors
joshieDo Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/contracts/src/precompiles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ pub const SYSTEM_PRECOMPILES: &[(Address, TempoHardfork)] = &[
(STORAGE_CREDITS_ADDRESS, TempoHardfork::T7),
(CURRENT_COMMITTEE_ADDRESS, TempoHardfork::T8),
(ZONE_FACTORY_ADDRESS, TempoHardfork::T10),
(NATIVE_MULTISIG_ADDRESS, TempoHardfork::T11),
];
37 changes: 32 additions & 5 deletions crates/evm/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ use std::collections::{HashMap, HashSet};
use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
use tempo_contracts::precompiles::{
ADDRESS_REGISTRY_ADDRESS, CURRENT_COMMITTEE_ADDRESS, ICurrentCommittee, INITIAL_FACTORY_OWNER,
RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS, STORAGE_CREDITS_ADDRESS,
TIP20_CHANNEL_RESERVE_ADDRESS, VALIDATOR_CONFIG_V2_ADDRESS, initial_zone_factory_state,
NATIVE_MULTISIG_ADDRESS, RECEIVE_POLICY_GUARD_ADDRESS, SIGNATURE_VERIFIER_ADDRESS,
STORAGE_CREDITS_ADDRESS, TIP20_CHANNEL_RESERVE_ADDRESS, VALIDATOR_CONFIG_V2_ADDRESS,
initial_zone_factory_state,
};
use tempo_primitives::{
SubBlock, SubBlockMetadata, TempoReceipt, TempoTxEnvelope, TempoTxType,
Expand Down Expand Up @@ -628,6 +629,9 @@ where
if self.inner.spec.is_t8_active_at_timestamp(timestamp) {
self.deploy_precompile_at_boundary(CURRENT_COMMITTEE_ADDRESS, &[])?;
}
if self.inner.spec.is_t11_active_at_timestamp(timestamp) {
self.deploy_precompile_at_boundary(NATIVE_MULTISIG_ADDRESS, &[])?;
}
if self.inner.spec.is_t10_active_at_timestamp(timestamp) {
self.deploy_zone_factory_at_boundary()?;
}
Expand Down Expand Up @@ -1967,22 +1971,45 @@ mod tests {
}

#[test]
fn test_apply_pre_execution_deploys_guard_code() {
// Dev chainspec has t6Time: 0, so T6 is active at any timestamp.
let chainspec = Arc::new(TempoChainSpec::from_genesis(DEV.genesis().clone()));
fn test_apply_pre_execution_pre_t11_does_not_deploy_native_multisig_code() {
let chainspec = test_chainspec();
let mut db = State::builder().with_bundle_update().build();
let mut executor = TestExecutorBuilder::default()
.with_parent_beacon_block_root(B256::ZERO)
.build(&mut db, &chainspec);
executor.evm_mut().ctx_mut().block.inner.timestamp = U256::from(u64::MAX);

executor.apply_pre_execution_changes().unwrap();
drop(executor);

let acc = db.load_cache_account(NATIVE_MULTISIG_ADDRESS).unwrap();
let info = acc.account_info();
assert!(
info.is_none() || info.unwrap().is_empty_code_hash(),
"NativeMultisig code should not be deployed before T11"
);

let acc = db.load_cache_account(RECEIVE_POLICY_GUARD_ADDRESS).unwrap();
let info = acc.account_info().unwrap();
assert!(!info.is_empty_code_hash());
}

#[test]
fn test_apply_pre_execution_deploys_t11_native_multisig_code() {
let chainspec = Arc::new(TempoChainSpec::from_genesis(DEV.genesis().clone()));
let mut db = State::builder().with_bundle_update().build();
let mut executor = TestExecutorBuilder::default()
.with_parent_beacon_block_root(B256::ZERO)
.build(&mut db, &chainspec);

executor.apply_pre_execution_changes().unwrap();
drop(executor);

let acc = db.load_cache_account(NATIVE_MULTISIG_ADDRESS).unwrap();
let info = acc.account_info().unwrap();
assert!(!info.is_empty_code_hash());
}

#[test]
fn test_pre_t3_does_not_deploy_signature_verifier_code() {
// Moderato does not have T4 active (no t3Time set), so the code should NOT be deployed.
Expand Down
12 changes: 10 additions & 2 deletions crates/evm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,10 @@ mod tests {
zones::{ZONE_MESSENGER_RUNTIME, ZONE_PORTAL_RUNTIME},
};
use tempo_precompiles::{
NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS, STORAGE_CREDITS_ADDRESS,
TIP_FEE_MANAGER_ADDRESS, TIP403_REGISTRY_ADDRESS,
NATIVE_MULTISIG_ADDRESS, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS,
STORAGE_CREDITS_ADDRESS, TIP_FEE_MANAGER_ADDRESS, TIP403_REGISTRY_ADDRESS,
error::TempoPrecompileError,
native_multisig::NativeMultisig,
storage::{ContractStorage, StorageAction, StorageActions, StorageCtx, StorageKey},
storage_credits::StorageCredits,
test_util::TIP20Setup,
Expand Down Expand Up @@ -1510,6 +1511,10 @@ mod tests {
let nonce_key = U256::from(42);
let sender_nonce_key_slot = nonce_key
.mapping_slot(sender.mapping_slot(tempo_precompiles::nonce::slots::NONCES));
let (sender_multisig_account_slot, _) =
NativeMultisig::account_threshold_storage_slot(sender);
let (recipient_multisig_account_slot, _) =
NativeMultisig::account_threshold_storage_slot(recipient);

#[rustfmt::skip]
let labels = StorageActionSnapshotLabels {
Expand All @@ -1521,6 +1526,7 @@ mod tests {
(TIP403_REGISTRY_ADDRESS, "TIP403_REGISTRY"),
(STORAGE_CREDITS_ADDRESS, "STORAGE_CREDITS"),
(NONCE_PRECOMPILE_ADDRESS, "NONCE_MANAGER"),
(NATIVE_MULTISIG_ADDRESS, "NATIVE_MULTISIG"),
]),
slots: BTreeMap::from([
((TIP_FEE_MANAGER_ADDRESS, validator_token_slot), "validatorTokens[beneficiary]"),
Expand All @@ -1536,6 +1542,8 @@ mod tests {
((STORAGE_CREDITS_ADDRESS, StorageCredits::slot(fee_token)), "storageCredits[FEE_TOKEN]"),
((STORAGE_CREDITS_ADDRESS, StorageCredits::slot(two_hop_fee_token)), "storageCredits[TWO_HOP_FEE_TOKEN]"),
((NONCE_PRECOMPILE_ADDRESS, sender_nonce_key_slot), "nonces[sender][42]"),
((NATIVE_MULTISIG_ADDRESS, sender_multisig_account_slot), "accounts[sender]"),
((NATIVE_MULTISIG_ADDRESS, recipient_multisig_account_slot), "accounts[recipient]"),
]),
tip20_slots: BTreeMap::from([
(tip20_slots::CURRENCY, "currency"),
Expand Down
7 changes: 1 addition & 6 deletions crates/node/tests/it/tempo_transaction/runners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1752,11 +1752,7 @@ pub(super) async fn run_fill_sign_send<E: TestEnv>(
let tx_hash = if uses_p256 {
let (signing_key, pub_key_x, pub_key_y, signer_addr) = generate_p256_access_key();

// In the E2E fill flow, P256/WebAuthn signers use a fee payer
// to cover gas (the fee_payer flag on FillTestCase is not checked
// here because eth_fillTransaction always requires one).
let fee_payer_signer = PrivateKeySigner::random();
let _ = env.fund_account(fee_payer_signer.address()).await?;
let _ = env.fund_account(signer_addr).await?;

let current_timestamp = env.current_block_timestamp().await?;

Expand Down Expand Up @@ -1790,7 +1786,6 @@ pub(super) async fn run_fill_sign_send<E: TestEnv>(
"eth_fillTransaction should not set fee_token (client must set it)"
);
tx.fee_token = Some(DEFAULT_FEE_TOKEN);
sign_fee_payer(&mut tx, signer_addr, &fee_payer_signer)?;

let signature = match test_case.key_type {
KeyType::P256 => sign_aa_tx_p256(&tx, &signing_key, pub_key_x, pub_key_y)?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,74 +21,74 @@ baseline: 274318
"webauthn::batch_5_transfers": 574113
"webauthn::batch_10_transfers": 603050
"webauthn::contract_creation": 785926
"key_auth_p256_0_limits::noop": 538498
"key_auth_secp256k1_0_limits::noop": 538498
"key_auth_webauthn_0_limits::noop": 538498
"key_auth_secp256k1_witness::noop": 542127
"key_auth_p256_0_limits::transfer": 808668
"key_auth_secp256k1_0_limits::transfer": 808668
"key_auth_webauthn_0_limits::transfer": 808668
"key_auth_secp256k1_witness::transfer": 812296
"key_auth_p256_0_limits::batch_2_transfers": 814455
"key_auth_secp256k1_0_limits::batch_2_transfers": 814455
"key_auth_webauthn_0_limits::batch_2_transfers": 814455
"key_auth_secp256k1_witness::batch_2_transfers": 818084
"key_auth_p256_0_limits::batch_5_transfers": 831818
"key_auth_secp256k1_0_limits::batch_5_transfers": 831818
"key_auth_webauthn_0_limits::batch_5_transfers": 831818
"key_auth_secp256k1_witness::batch_5_transfers": 835446
"key_auth_p256_0_limits::batch_10_transfers": 860756
"key_auth_secp256k1_0_limits::batch_10_transfers": 860756
"key_auth_webauthn_0_limits::batch_10_transfers": 860756
"key_auth_secp256k1_witness::batch_10_transfers": 864384
"key_auth_p256_1_limit::noop": 1042466
"key_auth_secp256k1_1_limit::noop": 1042466
"key_auth_webauthn_1_limit::noop": 1042466
"key_auth_secp256k1_witness_1_limit::noop": 1046095
"key_auth_p256_1_limit::transfer": 1312636
"key_auth_secp256k1_1_limit::transfer": 1312636
"key_auth_webauthn_1_limit::transfer": 1312636
"key_auth_secp256k1_witness_1_limit::transfer": 1316264
"key_auth_p256_1_limit::batch_2_transfers": 1318423
"key_auth_secp256k1_1_limit::batch_2_transfers": 1318423
"key_auth_webauthn_1_limit::batch_2_transfers": 1318423
"key_auth_secp256k1_witness_1_limit::batch_2_transfers": 1322052
"key_auth_p256_1_limit::batch_5_transfers": 1335786
"key_auth_secp256k1_1_limit::batch_5_transfers": 1335786
"key_auth_webauthn_1_limit::batch_5_transfers": 1335786
"key_auth_secp256k1_witness_1_limit::batch_5_transfers": 1339415
"key_auth_p256_1_limit::batch_10_transfers": 1364724
"key_auth_secp256k1_1_limit::batch_10_transfers": 1364724
"key_auth_webauthn_1_limit::batch_10_transfers": 1364724
"key_auth_secp256k1_witness_1_limit::batch_10_transfers": 1368352
"key_auth_secp256k1_target_any_selector::transfer": 1823660
"key_auth_p256_3_limits::noop": 2050403
"key_auth_secp256k1_3_limits::noop": 2050403
"key_auth_webauthn_3_limits::noop": 2050403
"key_auth_p256_3_limits::transfer": 2320572
"key_auth_secp256k1_3_limits::transfer": 2320572
"key_auth_webauthn_3_limits::transfer": 2320572
"key_auth_p256_3_limits::batch_2_transfers": 2326360
"key_auth_secp256k1_3_limits::batch_2_transfers": 2326360
"key_auth_webauthn_3_limits::batch_2_transfers": 2326360
"key_auth_p256_3_limits::batch_5_transfers": 2343723
"key_auth_secp256k1_3_limits::batch_5_transfers": 2343723
"key_auth_webauthn_3_limits::batch_5_transfers": 2343723
"key_auth_p256_3_limits::batch_10_transfers": 2372660
"key_auth_secp256k1_3_limits::batch_10_transfers": 2372660
"key_auth_webauthn_3_limits::batch_10_transfers": 2372660
"key_auth_secp256k1_selector_any_recipient::transfer": 2586668
"key_auth_secp256k1_selector_recipient::transfer": 3347660
"keychain_secp256k1::noop": 541623
"keychain_secp256k1::transfer": 811994
"keychain_secp256k1::batch_2_transfers": 818084
"keychain_secp256k1::batch_5_transfers": 836354
"keychain_secp256k1::batch_10_transfers": 866803
"keychain_p256::noop": 546662
"keychain_p256::transfer": 817033
"keychain_p256::batch_2_transfers": 823123
"keychain_p256::batch_5_transfers": 841393
"keychain_p256::batch_10_transfers": 871843
"keychain_secp256k1_selector_any_recipient::transfer": 2592413
"keychain_secp256k1_selector_recipient::transfer": 3351490
"keychain_secp256k1_target_any_selector::transfer": 1827187
"key_auth_p256_0_limits::noop": 540615
"key_auth_secp256k1_0_limits::noop": 540615
"key_auth_webauthn_0_limits::noop": 540615
"key_auth_secp256k1_witness::noop": 544243
"key_auth_p256_0_limits::transfer": 810784
"key_auth_secp256k1_0_limits::transfer": 810784
"key_auth_webauthn_0_limits::transfer": 810784
"key_auth_secp256k1_witness::transfer": 814413
"key_auth_p256_0_limits::batch_2_transfers": 816572
"key_auth_secp256k1_0_limits::batch_2_transfers": 816572
"key_auth_webauthn_0_limits::batch_2_transfers": 816572
"key_auth_secp256k1_witness::batch_2_transfers": 820200
"key_auth_p256_0_limits::batch_5_transfers": 833934
"key_auth_secp256k1_0_limits::batch_5_transfers": 833934
"key_auth_webauthn_0_limits::batch_5_transfers": 833934
"key_auth_secp256k1_witness::batch_5_transfers": 837563
"key_auth_p256_0_limits::batch_10_transfers": 862872
"key_auth_secp256k1_0_limits::batch_10_transfers": 862872
"key_auth_webauthn_0_limits::batch_10_transfers": 862872
"key_auth_secp256k1_witness::batch_10_transfers": 866501
"key_auth_p256_1_limit::noop": 1044583
"key_auth_secp256k1_1_limit::noop": 1044583
"key_auth_webauthn_1_limit::noop": 1044583
"key_auth_secp256k1_witness_1_limit::noop": 1048212
"key_auth_p256_1_limit::transfer": 1314752
"key_auth_secp256k1_1_limit::transfer": 1314752
"key_auth_webauthn_1_limit::transfer": 1314752
"key_auth_secp256k1_witness_1_limit::transfer": 1318381
"key_auth_p256_1_limit::batch_2_transfers": 1320540
"key_auth_secp256k1_1_limit::batch_2_transfers": 1320540
"key_auth_webauthn_1_limit::batch_2_transfers": 1320540
"key_auth_secp256k1_witness_1_limit::batch_2_transfers": 1324169
"key_auth_p256_1_limit::batch_5_transfers": 1337903
"key_auth_secp256k1_1_limit::batch_5_transfers": 1337903
"key_auth_webauthn_1_limit::batch_5_transfers": 1337903
"key_auth_secp256k1_witness_1_limit::batch_5_transfers": 1341531
"key_auth_p256_1_limit::batch_10_transfers": 1366841
"key_auth_secp256k1_1_limit::batch_10_transfers": 1366841
"key_auth_webauthn_1_limit::batch_10_transfers": 1366841
"key_auth_secp256k1_witness_1_limit::batch_10_transfers": 1370469
"key_auth_secp256k1_target_any_selector::transfer": 1825776
"key_auth_p256_3_limits::noop": 2052520
"key_auth_secp256k1_3_limits::noop": 2052520
"key_auth_webauthn_3_limits::noop": 2052520
"key_auth_p256_3_limits::transfer": 2322689
"key_auth_secp256k1_3_limits::transfer": 2322689
"key_auth_webauthn_3_limits::transfer": 2322689
"key_auth_p256_3_limits::batch_2_transfers": 2328477
"key_auth_secp256k1_3_limits::batch_2_transfers": 2328477
"key_auth_webauthn_3_limits::batch_2_transfers": 2328477
"key_auth_p256_3_limits::batch_5_transfers": 2345839
"key_auth_secp256k1_3_limits::batch_5_transfers": 2345839
"key_auth_webauthn_3_limits::batch_5_transfers": 2345839
"key_auth_p256_3_limits::batch_10_transfers": 2374777
"key_auth_secp256k1_3_limits::batch_10_transfers": 2374777
"key_auth_webauthn_3_limits::batch_10_transfers": 2374777
"key_auth_secp256k1_selector_any_recipient::transfer": 2588784
"key_auth_secp256k1_selector_recipient::transfer": 3349776
"keychain_secp256k1::noop": 543739
"keychain_secp256k1::transfer": 814110
"keychain_secp256k1::batch_2_transfers": 820200
"keychain_secp256k1::batch_5_transfers": 838470
"keychain_secp256k1::batch_10_transfers": 868920
"keychain_p256::noop": 548779
"keychain_p256::transfer": 819150
"keychain_p256::batch_2_transfers": 825240
"keychain_p256::batch_5_transfers": 843510
"keychain_p256::batch_10_transfers": 873960
"keychain_secp256k1_selector_any_recipient::transfer": 2594529
"keychain_secp256k1_selector_recipient::transfer": 3353606
"keychain_secp256k1_target_any_selector::transfer": 1829304
Loading
Loading