Skip to content

AlphaPrecompile view functions perform Substrate work without charging EVM gas #2741

@gztensor

Description

@gztensor

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_swapdo_swap(simulate=true)transactional::with_transactionswap_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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions