-
Notifications
You must be signed in to change notification settings - Fork 316
AlphaPrecompile view functions perform Substrate work without charging EVM gas #2741
Description
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) 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 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.