diff --git a/stellar/Cargo.lock b/stellar/Cargo.lock index cf9479b..5d0f046 100644 --- a/stellar/Cargo.lock +++ b/stellar/Cargo.lock @@ -2164,10 +2164,12 @@ dependencies = [ name = "wraith-stellar-bench" version = "0.1.0" dependencies = [ + "governance", "soroban-sdk", "stealth-announcer", "stealth-registry", "stealth-sender", + "stealth-splitter", "stealth-vault", "wraith-names", ] diff --git a/stellar/PERF.md b/stellar/PERF.md index f411777..11a5410 100644 --- a/stellar/PERF.md +++ b/stellar/PERF.md @@ -68,7 +68,7 @@ ownership checks. ## Current Numbers -These are the harness results auto-updated from `develop` (measured 2026-08-26, commit `b22ae3326438`). +These are the harness results auto-updated from `develop` (measured 2026-08-27, commit `bfdd95f90343`). | Contract | Function | Parameters | Instructions | Mem bytes | Read entries | Write entries | Read bytes | Write bytes | Event bytes | |---|---|---:|---:|---:|---:|---:|---:|---:|---:| @@ -85,17 +85,36 @@ These are the harness results auto-updated from `develop` (measured 2026-08-26, | stealth-sender | batch_send | batch_size=5 | 853185 | 125400 | 6 | 7 | 1232 | 1416 | 3056 | | stealth-sender | batch_send | batch_size=10 | 1674743 | 250790 | 6 | 12 | 1232 | 2536 | 5356 | | stealth-sender | batch_send | batch_size=25 | 4352799 | 695660 | 6 | 27 | 1232 | 5896 | 12256 | +| stealth-sender | withdraw_many | entries=1 | 166455 | 24918 | 4 | 3 | 888 | 520 | 616 | +| stealth-sender | withdraw_many | entries=10 | 1478512 | 218040 | 4 | 12 | 888 | 2536 | 4756 | +| stealth-sender | withdraw_many | entries=30 | 4826570 | 760300 | 4 | 32 | 888 | 7016 | 13956 | | wraith-names | register | name_len=3 | 88106 | 10944 | 2 | 3 | 104 | 568 | 348 | | wraith-names | register | name_len=32 | 88654 | 10914 | 2 | 3 | 104 | 596 | 376 | | wraith-names | resolve | hit | 39126 | 4010 | 2 | 0 | 440 | 0 | 144 | | wraith-names | resolve | miss | 27723 | 2655 | 2 | 0 | 104 | 0 | 0 | | wraith-names | name_of | hit | 49222 | 4865 | 3 | 0 | 604 | 0 | 0 | | wraith-names | name_of | miss | 25374 | 2127 | 2 | 0 | 104 | 0 | 0 | +| wraith-names | bulk_register | count=5 | 537147 | 62539 | 2 | 11 | 104 | 2552 | 2060 | +| wraith-names | bulk_register | count=10 | 1181877 | 155599 | 2 | 21 | 104 | 5032 | 4000 | +| wraith-names | bulk_register | count=20 | 2811135 | 442669 | 2 | 41 | 104 | 9992 | 7880 | +| wraith-names | bulk_renew | count=5 | 351158 | 25489 | 11 | 0 | 2584 | 0 | 1032 | +| wraith-names | bulk_renew | count=10 | 718431 | 51559 | 21 | 0 | 5064 | 0 | 1812 | +| wraith-names | bulk_renew | count=20 | 1511175 | 111799 | 41 | 0 | 10024 | 0 | 3372 | +| wraith-names | extend_name_ttl | existing | 61349 | 5506 | 4 | 0 | 656 | 0 | 264 | +| stealth-splitter | create_split | beneficiaries=5 | 121823 | 14396 | 1 | 2 | 180 | 1300 | 544 | +| stealth-splitter | create_split | beneficiaries=15 | 230548 | 38036 | 1 | 2 | 180 | 2700 | 544 | +| stealth-splitter | create_split | beneficiaries=25 | 343273 | 77676 | 1 | 2 | 180 | 4100 | 544 | +| stealth-splitter | fund_split | beneficiaries=5 | 946972 | 146219 | 4 | 8 | 2116 | 2644 | 2848 | +| stealth-splitter | fund_split | beneficiaries=15 | 2755375 | 439179 | 4 | 18 | 3516 | 6284 | 7448 | +| stealth-splitter | fund_split | beneficiaries=25 | 4730305 | 777939 | 4 | 28 | 4916 | 9924 | 12048 | | stealth-vault | deposit | asset=xlm | 262123 | 39207 | 6 | 4 | 1228 | 924 | 1084 | | stealth-vault | deposit | asset=issued | 261883 | 39207 | 6 | 4 | 1228 | 924 | 1084 | | stealth-vault | claim | unlocked | 201891 | 32853 | 4 | 4 | 1476 | 520 | 628 | | stealth-vault | refund | depositor | 197513 | 30286 | 3 | 4 | 1608 | 520 | 628 | | stealth-vault | refund_permissionless | keeper | 210287 | 32111 | 3 | 4 | 1608 | 520 | 628 | +| governance | propose | happy_path | 116975 | 18098 | 2 | 2 | 464 | 964 | 320 | +| governance | vote | happy_path | 236214 | 43742 | 4 | 3 | 1636 | 1164 | 364 | +| governance | execute | happy_path | 164625 | 29805 | 1 | 2 | 1048 | 1040 | 256 | ## Gas Regression Gate diff --git a/stellar/bench/Cargo.toml b/stellar/bench/Cargo.toml index 0693d61..7db7fc3 100644 --- a/stellar/bench/Cargo.toml +++ b/stellar/bench/Cargo.toml @@ -8,8 +8,10 @@ soroban-sdk = { workspace = true, features = ["testutils"] } stealth-announcer = { path = "../stealth-announcer", features = ["testutils"] } stealth-registry = { path = "../stealth-registry", features = ["testutils"] } stealth-sender = { path = "../stealth-sender", features = ["testutils"] } +stealth-splitter = { path = "../stealth-splitter", features = ["testutils"] } stealth-vault = { path = "../stealth-vault", features = ["testutils"] } wraith-names = { path = "../wraith-names", features = ["testutils"] } +governance = { path = "../contracts/governance", features = ["testutils"] } [[bench]] name = "gas" diff --git a/stellar/bench/baseline.json b/stellar/bench/baseline.json index 0313fc1..c8e67fd 100644 --- a/stellar/bench/baseline.json +++ b/stellar/bench/baseline.json @@ -1,6 +1,6 @@ { - "generated_at": "1787666620", - "commit": "d8e51779e61d77153b5c85adbf6ec4ca9c2aa847", + "generated_at": "1787801755", + "commit": "bfdd95f90343b03a28ae3c090b64322d0ee14558", "threshold_pct": 5, "results": [ { @@ -159,6 +159,42 @@ "write_bytes": 5896, "events_bytes": 12256 }, + { + "contract": "stealth-sender", + "function": "withdraw_many", + "params": "entries=1", + "instructions": 166455, + "mem_bytes": 24918, + "read_entries": 4, + "write_entries": 3, + "read_bytes": 888, + "write_bytes": 520, + "events_bytes": 616 + }, + { + "contract": "stealth-sender", + "function": "withdraw_many", + "params": "entries=10", + "instructions": 1478512, + "mem_bytes": 218040, + "read_entries": 4, + "write_entries": 12, + "read_bytes": 888, + "write_bytes": 2536, + "events_bytes": 4756 + }, + { + "contract": "stealth-sender", + "function": "withdraw_many", + "params": "entries=30", + "instructions": 4826570, + "mem_bytes": 760300, + "read_entries": 4, + "write_entries": 32, + "read_bytes": 888, + "write_bytes": 7016, + "events_bytes": 13956 + }, { "contract": "wraith-names", "function": "register", @@ -230,6 +266,258 @@ "read_bytes": 104, "write_bytes": 0, "events_bytes": 0 + }, + { + "contract": "wraith-names", + "function": "bulk_register", + "params": "count=5", + "instructions": 537147, + "mem_bytes": 62539, + "read_entries": 2, + "write_entries": 11, + "read_bytes": 104, + "write_bytes": 2552, + "events_bytes": 2060 + }, + { + "contract": "wraith-names", + "function": "bulk_register", + "params": "count=10", + "instructions": 1181877, + "mem_bytes": 155599, + "read_entries": 2, + "write_entries": 21, + "read_bytes": 104, + "write_bytes": 5032, + "events_bytes": 4000 + }, + { + "contract": "wraith-names", + "function": "bulk_register", + "params": "count=20", + "instructions": 2811135, + "mem_bytes": 442669, + "read_entries": 2, + "write_entries": 41, + "read_bytes": 104, + "write_bytes": 9992, + "events_bytes": 7880 + }, + { + "contract": "wraith-names", + "function": "bulk_renew", + "params": "count=5", + "instructions": 351158, + "mem_bytes": 25489, + "read_entries": 11, + "write_entries": 0, + "read_bytes": 2584, + "write_bytes": 0, + "events_bytes": 1032 + }, + { + "contract": "wraith-names", + "function": "bulk_renew", + "params": "count=10", + "instructions": 718431, + "mem_bytes": 51559, + "read_entries": 21, + "write_entries": 0, + "read_bytes": 5064, + "write_bytes": 0, + "events_bytes": 1812 + }, + { + "contract": "wraith-names", + "function": "bulk_renew", + "params": "count=20", + "instructions": 1511175, + "mem_bytes": 111799, + "read_entries": 41, + "write_entries": 0, + "read_bytes": 10024, + "write_bytes": 0, + "events_bytes": 3372 + }, + { + "contract": "wraith-names", + "function": "extend_name_ttl", + "params": "existing", + "instructions": 61349, + "mem_bytes": 5506, + "read_entries": 4, + "write_entries": 0, + "read_bytes": 656, + "write_bytes": 0, + "events_bytes": 264 + }, + { + "contract": "stealth-splitter", + "function": "create_split", + "params": "beneficiaries=5", + "instructions": 121823, + "mem_bytes": 14396, + "read_entries": 1, + "write_entries": 2, + "read_bytes": 180, + "write_bytes": 1300, + "events_bytes": 544 + }, + { + "contract": "stealth-splitter", + "function": "create_split", + "params": "beneficiaries=15", + "instructions": 230548, + "mem_bytes": 38036, + "read_entries": 1, + "write_entries": 2, + "read_bytes": 180, + "write_bytes": 2700, + "events_bytes": 544 + }, + { + "contract": "stealth-splitter", + "function": "create_split", + "params": "beneficiaries=25", + "instructions": 343273, + "mem_bytes": 77676, + "read_entries": 1, + "write_entries": 2, + "read_bytes": 180, + "write_bytes": 4100, + "events_bytes": 544 + }, + { + "contract": "stealth-splitter", + "function": "fund_split", + "params": "beneficiaries=5", + "instructions": 946972, + "mem_bytes": 146219, + "read_entries": 4, + "write_entries": 8, + "read_bytes": 2116, + "write_bytes": 2644, + "events_bytes": 2848 + }, + { + "contract": "stealth-splitter", + "function": "fund_split", + "params": "beneficiaries=15", + "instructions": 2755375, + "mem_bytes": 439179, + "read_entries": 4, + "write_entries": 18, + "read_bytes": 3516, + "write_bytes": 6284, + "events_bytes": 7448 + }, + { + "contract": "stealth-splitter", + "function": "fund_split", + "params": "beneficiaries=25", + "instructions": 4730305, + "mem_bytes": 777939, + "read_entries": 4, + "write_entries": 28, + "read_bytes": 4916, + "write_bytes": 9924, + "events_bytes": 12048 + }, + { + "contract": "stealth-vault", + "function": "deposit", + "params": "asset=xlm", + "instructions": 262123, + "mem_bytes": 39207, + "read_entries": 6, + "write_entries": 4, + "read_bytes": 1228, + "write_bytes": 924, + "events_bytes": 1084 + }, + { + "contract": "stealth-vault", + "function": "deposit", + "params": "asset=issued", + "instructions": 261883, + "mem_bytes": 39207, + "read_entries": 6, + "write_entries": 4, + "read_bytes": 1228, + "write_bytes": 924, + "events_bytes": 1084 + }, + { + "contract": "stealth-vault", + "function": "claim", + "params": "unlocked", + "instructions": 201891, + "mem_bytes": 32853, + "read_entries": 4, + "write_entries": 4, + "read_bytes": 1476, + "write_bytes": 520, + "events_bytes": 628 + }, + { + "contract": "stealth-vault", + "function": "refund", + "params": "depositor", + "instructions": 197513, + "mem_bytes": 30286, + "read_entries": 3, + "write_entries": 4, + "read_bytes": 1608, + "write_bytes": 520, + "events_bytes": 628 + }, + { + "contract": "stealth-vault", + "function": "refund_permissionless", + "params": "keeper", + "instructions": 210287, + "mem_bytes": 32111, + "read_entries": 3, + "write_entries": 4, + "read_bytes": 1608, + "write_bytes": 520, + "events_bytes": 628 + }, + { + "contract": "governance", + "function": "propose", + "params": "happy_path", + "instructions": 116975, + "mem_bytes": 18098, + "read_entries": 2, + "write_entries": 2, + "read_bytes": 464, + "write_bytes": 964, + "events_bytes": 320 + }, + { + "contract": "governance", + "function": "vote", + "params": "happy_path", + "instructions": 236214, + "mem_bytes": 43742, + "read_entries": 4, + "write_entries": 3, + "read_bytes": 1636, + "write_bytes": 1164, + "events_bytes": 364 + }, + { + "contract": "governance", + "function": "execute", + "params": "happy_path", + "instructions": 164625, + "mem_bytes": 29805, + "read_entries": 1, + "write_entries": 2, + "read_bytes": 1048, + "write_bytes": 1040, + "events_bytes": 256 } ] } diff --git a/stellar/bench/src/lib.rs b/stellar/bench/src/lib.rs index b6e26c0..ba44a90 100644 --- a/stellar/bench/src/lib.rs +++ b/stellar/bench/src/lib.rs @@ -1,12 +1,17 @@ use soroban_sdk::testutils::{Address as _, Ledger as _}; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{vec, Address, Bytes, BytesN, Env, String as SorobanString, Vec as SorobanVec}; +use soroban_sdk::{ + contract, contractimpl, symbol_short, vec, Address, Bytes, BytesN, Env, + String as SorobanString, Vec as SorobanVec, +}; +use governance::{GovernanceContract, GovernanceContractClient}; use stealth_announcer::{ StealthAnnouncerContract, StealthAnnouncerContractClient, STELLAR_V2_SCHEME_ID, }; use stealth_registry::{StealthRegistryContract, StealthRegistryContractClient}; -use stealth_sender::{StealthSenderContract, StealthSenderContractClient}; +use stealth_sender::{StealthSenderContract, StealthSenderContractClient, WithdrawalEntry}; +use stealth_splitter::{Beneficiary, StealthSplitterContract, StealthSplitterContractClient}; use stealth_vault::{StealthVaultContract, StealthVaultContractClient}; use wraith_names::{WraithNamesContract, WraithNamesContractClient}; @@ -141,6 +146,21 @@ pub fn collect_rows() -> std::vec::Vec { )); } + for entries in [1u32, 10, 30] { + rows.push(measure( + "stealth-sender", + "withdraw_many", + format!("entries={entries}"), + |env| { + env.mock_all_auths(); + let sender_contract_id = env.register(StealthSenderContract, ()); + let client = StealthSenderContractClient::new(env, &sender_contract_id); + let (token, withdrawer) = funded_token(env, true); + client.withdraw_many(&withdrawer, &withdraw_entries(env, &token, entries)); + }, + )); + } + for name_len in [3u32, 32] { rows.push(measure( "wraith-names", @@ -193,6 +213,112 @@ pub fn collect_rows() -> std::vec::Vec { let _ = client.try_name_of(&bytes(env, 64, 8)); })); + for count in [5u32, 10, 20] { + rows.push(measure( + "wraith-names", + "bulk_register", + format!("count={count}"), + |env| { + env.mock_all_auths(); + let contract_id = env.register(WraithNamesContract, ()); + let client = WraithNamesContractClient::new(env, &contract_id); + client.bulk_register( + &Address::generate(env), + &numbered_names(env, count), + &numbered_metas(env, count), + ); + }, + )); + } + + for count in [5u32, 10, 20] { + rows.push(measure( + "wraith-names", + "bulk_renew", + format!("count={count}"), + |env| { + env.mock_all_auths(); + let contract_id = env.register(WraithNamesContract, ()); + let client = WraithNamesContractClient::new(env, &contract_id); + let owner = Address::generate(env); + let names = numbered_names(env, count); + let metas = numbered_metas(env, count); + for i in 0..count { + client.register(&owner, &names.get(i).unwrap(), &metas.get(i).unwrap()); + } + client.bulk_renew(&names, &(env.ledger().sequence() + 10_000)); + }, + )); + } + + rows.push(measure( + "wraith-names", + "extend_name_ttl", + "existing".into(), + |env| { + env.mock_all_auths(); + let contract_id = env.register(WraithNamesContract, ()); + let client = WraithNamesContractClient::new(env, &contract_id); + let n = SorobanString::from_str(env, "alice"); + client.register(&Address::generate(env), &n, &bytes(env, 64, 9)); + client.extend_name_ttl(&n, &(env.ledger().sequence() + 10_000)); + }, + )); + + for n in [5u32, 15, 25] { + rows.push(measure( + "stealth-splitter", + "create_split", + format!("beneficiaries={n}"), + |env| { + env.mock_all_auths(); + let client = splitter_client(env); + client.create_split( + &Address::generate(env), + &split_beneficiaries(env, n), + &Address::generate(env), + &bytes(env, 8, 1), + ); + }, + )); + } + + for n in [5u32, 15, 25] { + rows.push(measure( + "stealth-splitter", + "fund_split", + format!("beneficiaries={n}"), + |env| { + env.mock_all_auths(); + let client = splitter_client(env); + let (token, funder) = funded_token(env, true); + let split_id = client.create_split( + &Address::generate(env), + &split_beneficiaries(env, n), + &token, + &bytes(env, 8, 2), + ); + let mut addresses: SorobanVec
= vec![env]; + let mut keys: SorobanVec> = vec![env]; + let mut metadatas: SorobanVec = vec![env]; + for i in 0..n { + addresses.push_back(Address::generate(env)); + keys.push_back(BytesN::from_array(env, &[i as u8; 32])); + metadatas.push_back(bytes(env, 32, i as u8)); + } + client.fund_split( + &funder, + &split_id, + &1_000, + &STELLAR_V2_SCHEME_ID, + &addresses, + &keys, + &metadatas, + ); + }, + )); + } + for asset in ["xlm", "issued"] { rows.push(measure( "stealth-vault", @@ -281,6 +407,63 @@ pub fn collect_rows() -> std::vec::Vec { }, )); + rows.push(measure( + "governance", + "propose", + "happy_path".into(), + |env| { + env.mock_all_auths(); + let (client, _token, target) = governance_client(env); + client.propose( + &Address::generate(env), + &target, + &symbol_short!("set_value"), + &bytes(env, 16, 1), + &SorobanString::from_str(env, "bench"), + ); + }, + )); + + rows.push(measure("governance", "vote", "happy_path".into(), |env| { + env.mock_all_auths(); + let (client, token, target) = governance_client(env); + let voter = Address::generate(env); + StellarAssetClient::new(env, &token).mint(&voter, &200); + let pid = client.propose( + &Address::generate(env), + &target, + &symbol_short!("set_value"), + &bytes(env, 16, 1), + &SorobanString::from_str(env, "bench"), + ); + client.vote(&voter, &pid, &true); + })); + + rows.push(measure( + "governance", + "execute", + "happy_path".into(), + |env| { + env.mock_all_auths(); + let (client, token, target) = governance_client(env); + let voter = Address::generate(env); + StellarAssetClient::new(env, &token).mint(&voter, &200); + let pid = client.propose( + &Address::generate(env), + &target, + &symbol_short!("set_value"), + &bytes(env, 16, 1), + &SorobanString::from_str(env, "bench"), + ); + client.vote(&voter, &pid, &true); + let proposal = client.get_proposal(&pid); + env.ledger().with_mut(|li| { + li.sequence_number = proposal.end_ledger + 20; + }); + client.execute(&pid); + }, + )); + rows } @@ -305,6 +488,10 @@ pub fn print_markdown(rows: &[Row]) { } /// Serialize bench rows to a stable JSON document used for CI baselines. +/// +/// Every row uses the same object shape (`contract`, `function`, `params`, +/// resource counters). New `collect_rows` cases are extra `results` entries; +/// existing keys and field names stay unchanged. pub fn to_json(rows: &[Row], meta: &BenchMeta) -> std::string::String { let mut out = std::string::String::new(); out.push_str("{\n"); @@ -353,6 +540,8 @@ pub fn to_json(rows: &[Row], meta: &BenchMeta) -> std::string::String { out } +/// Markdown table for PERF.md. One data row per collected case; column set is +/// fixed so new entrypoints do not change the existing table shape. pub fn markdown_table(rows: &[Row]) -> std::string::String { let mut out = std::string::String::new(); out.push_str("| Contract | Function | Parameters | Instructions | Mem bytes | Read entries | Write entries | Read bytes | Write bytes | Event bytes |\n"); @@ -499,3 +688,73 @@ fn name(env: &Env, len: u32) -> SorobanString { let raw = "abcdefghijklmnopqrstuvwxyz012345"; SorobanString::from_str(env, &raw[..len as usize]) } + +fn numbered_names(env: &Env, n: u32) -> SorobanVec { + let mut names = vec![env]; + for i in 0..n { + names.push_back(SorobanString::from_str(env, &format!("n{i:02}"))); + } + names +} + +fn numbered_metas(env: &Env, n: u32) -> SorobanVec { + let mut metas = vec![env]; + for i in 0..n { + metas.push_back(bytes(env, 64, i as u8)); + } + metas +} + +fn withdraw_entries(env: &Env, token: &Address, n: u32) -> SorobanVec { + let mut entries = vec![env]; + for _ in 0..n { + entries.push_back(WithdrawalEntry { + token: token.clone(), + to: Address::generate(env), + amount: 100, + }); + } + entries +} + +fn splitter_client(env: &Env) -> StealthSplitterContractClient<'static> { + let announcer_id = env.register(StealthAnnouncerContract, ()); + let splitter_id = env.register(StealthSplitterContract, ()); + let client = StealthSplitterContractClient::new(env, &splitter_id); + client.init(&announcer_id); + client +} + +fn split_beneficiaries(env: &Env, n: u32) -> SorobanVec { + let mut beneficiaries = vec![env]; + for i in 0..n { + beneficiaries.push_back(Beneficiary { + meta_address: bytes(env, 64, i as u8), + weight: 1, + }); + } + beneficiaries +} + +fn governance_client(env: &Env) -> (GovernanceContractClient<'static>, Address, Address) { + let token = env + .register_stellar_asset_contract_v2(Address::generate(env)) + .address(); + let gov_id = env.register(GovernanceContract, ()); + let client = GovernanceContractClient::new(env, &gov_id); + client.init(&Address::generate(env), &token, &100i128, &50u32, &10u32); + (client, token, env.register(BenchGovTarget, ())) +} + +/// Minimal target so `governance::execute` has a real contract to invoke. +#[contract] +struct BenchGovTarget; + +#[contractimpl] +impl BenchGovTarget { + pub fn set_value(env: Env, value: Bytes) { + env.storage() + .instance() + .set(&symbol_short!("value"), &value); + } +} diff --git a/stellar/contracts/governance/Cargo.toml b/stellar/contracts/governance/Cargo.toml index 73214c8..7cb48f6 100644 --- a/stellar/contracts/governance/Cargo.toml +++ b/stellar/contracts/governance/Cargo.toml @@ -7,6 +7,9 @@ description = "PoC on-chain governance for Wraith Protocol — NOT PRODUCTION RE [lib] crate-type = ["cdylib", "rlib"] +[features] +testutils = ["soroban-sdk/testutils"] + [dependencies] soroban-sdk = { workspace = true } wraith-metrics = { path = "../../wraith-metrics" } diff --git a/stellar/stealth-splitter/Cargo.toml b/stellar/stealth-splitter/Cargo.toml index 97e5e47..38ff75d 100644 --- a/stellar/stealth-splitter/Cargo.toml +++ b/stellar/stealth-splitter/Cargo.toml @@ -4,7 +4,10 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] + +[features] +testutils = ["soroban-sdk/testutils"] [dependencies] soroban-sdk = { workspace = true }