Description
The AlphaPrecompile (precompiles/src/alpha.rs, EVM address 2056) exposes several #[precompile::view] functions that perform non-trivial Substrate-side work — storage reads, full swap simulations through transactional::with_transaction, and unbounded iteration over all subnets — without calling handle.record_cost(...) to charge the EVM caller for that work.
Compare this against neighbouring precompiles which explicitly charge per DB read:
precompiles/src/subnet.rs:173 (get_network_registration_block) calls handle.record_cost(RuntimeHelper::<R>::db_read_gas_cost())?;
precompiles/src/staking.rs:499-500, 525-526, 550-552, 581-583, 616-618 carefully record one record_cost per DB read / write.
AlphaPrecompile, in the same workspace, does NOT:
get_alpha_price (alpha.rs:35-47) — 1+ reads, uncharged.
get_moving_alpha_price (alpha.rs:49-61) — read, uncharged.
get_tao_in_pool, get_alpha_in_pool, get_alpha_out_pool, get_alpha_issuance, get_tao_weight, get_ck_burn, get_subnet_mechanism, get_ema_price_halving_blocks, get_subnet_volume, get_tao_in_emission, get_alpha_in_emission, get_alpha_out_emission — all simple reads, all uncharged.
sim_swap_tao_for_alpha (alpha.rs:101-115) and sim_swap_alpha_for_tao (alpha.rs:117-131) — invoke SwapHandler::sim_swap → do_swap(simulate=true) → transactional::with_transaction → swap_inner. Inside the rollback transaction this performs: nested transactional layer push/pop, maybe_initialize_palswap (read+conditional 2 writes, even though rolled back), reserve reads, balancer math, SwapStep::execute (more reads). All uncharged.
get_sum_alpha_price (alpha.rs:190-215) — iterates NetworksAdded::<R>::iter() and per-iteration calls current_alpha_price (which reads SwapBalancer + SubnetTAO + SubnetAlphaIn). With the default SubnetLimit = 128 (pallets/subtensor/src/lib.rs:1092), this is up to ~128*3 ≈ 384 trie reads per call. All uncharged. Even worse, NetworksAdded::iter() is a full prefix iteration, which uses additional read cost per entry beyond the named storage map (one read per iteration step on top of the key decoding).
Recommendation
For each #[precompile::view] function in precompiles/src/alpha.rs, add handle.record_cost(RuntimeHelper::<R>::db_read_gas_cost())? calls reflecting the actual number of storage reads performed, mirroring the pattern in precompiles/src/staking.rs and precompiles/src/subnet.rs:173. For get_sum_alpha_price and sim_swap_*, the charge must scale with iteration / nested work — i.e. charge inside the loop, and charge for the writes done by maybe_initialize_palswap that would be rolled back but still consume execution weight.
Also consider returning a static extra_cost from is_precompile for compute-heavy precompile addresses, or wrapping these specific selectors with an additional gas-floor guard at dispatcher level.
Description
The
AlphaPrecompile(precompiles/src/alpha.rs, EVM address2056) exposes several#[precompile::view]functions that perform non-trivial Substrate-side work — storage reads, full swap simulations throughtransactional::with_transaction, and unbounded iteration over all subnets — without callinghandle.record_cost(...)to charge the EVM caller for that work.Compare this against neighbouring precompiles which explicitly charge per DB read:
precompiles/src/subnet.rs:173(get_network_registration_block) callshandle.record_cost(RuntimeHelper::<R>::db_read_gas_cost())?;precompiles/src/staking.rs:499-500, 525-526, 550-552, 581-583, 616-618carefully record onerecord_costper DB read / write.AlphaPrecompile, in the same workspace, does NOT:get_alpha_price(alpha.rs:35-47) — 1+ reads, uncharged.get_moving_alpha_price(alpha.rs:49-61) — read, uncharged.get_tao_in_pool,get_alpha_in_pool,get_alpha_out_pool,get_alpha_issuance,get_tao_weight,get_ck_burn,get_subnet_mechanism,get_ema_price_halving_blocks,get_subnet_volume,get_tao_in_emission,get_alpha_in_emission,get_alpha_out_emission— all simple reads, all uncharged.sim_swap_tao_for_alpha(alpha.rs:101-115) andsim_swap_alpha_for_tao(alpha.rs:117-131) — invokeSwapHandler::sim_swap→do_swap(simulate=true)→transactional::with_transaction→swap_inner. Inside the rollback transaction this performs: nested transactional layer push/pop,maybe_initialize_palswap(read+conditional 2 writes, even though rolled back), reserve reads, balancer math,SwapStep::execute(more reads). All uncharged.get_sum_alpha_price(alpha.rs:190-215) — iteratesNetworksAdded::<R>::iter()and per-iteration callscurrent_alpha_price(which readsSwapBalancer+SubnetTAO+SubnetAlphaIn). With the defaultSubnetLimit = 128(pallets/subtensor/src/lib.rs:1092), this is up to ~128*3 ≈ 384 trie reads per call. All uncharged. Even worse,NetworksAdded::iter()is a full prefix iteration, which uses additional read cost per entry beyond the named storage map (one read per iteration step on top of the key decoding).Recommendation
For each
#[precompile::view]function inprecompiles/src/alpha.rs, addhandle.record_cost(RuntimeHelper::<R>::db_read_gas_cost())?calls reflecting the actual number of storage reads performed, mirroring the pattern inprecompiles/src/staking.rsandprecompiles/src/subnet.rs:173. Forget_sum_alpha_priceandsim_swap_*, the charge must scale with iteration / nested work — i.e. charge inside the loop, and charge for the writes done bymaybe_initialize_palswapthat would be rolled back but still consume execution weight.Also consider returning a static
extra_costfromis_precompilefor compute-heavy precompile addresses, or wrapping these specific selectors with an additional gas-floor guard at dispatcher level.