Skip to content

Add gas accounting tests for confidential transfers - #418

Draft
ganymedio wants to merge 2 commits into
confidential-asset-prodfrom
test/ca-gas-assertions
Draft

Add gas accounting tests for confidential transfers#418
ganymedio wants to merge 2 commits into
confidential-asset-prodfrom
test/ca-gas-assertions

Conversation

@ganymedio

@ganymedio ganymedio commented Aug 24, 2026

Copy link
Copy Markdown

What

Two tests for the gas accounting of a confidential transfer.

confidential_transfer_batch_rangeproof_gas_is_version_gated runs the same transfer at the current gas feature version and at RELEASE_V1_27, asserting that it succeeds at both and that the gas difference is exactly bulletproofs.verify.base_batch_8_bits_16 plus bulletproofs.verify.base_batch_4_bits_16, over the gas unit scaling factor.

confidential_transfer_gas_profile breaks a transfer down by operation with the gas profiler, asserting the two batched range proofs are charged exactly their parameters and remain the largest single line item, and printing the rest so a shift in composition is visible.

Why the version gate matters

A transfer verifies two batched range proofs, over the 8 chunks of the new balance and the 4 chunks of the transfer amount. Their gas parameters are gated RELEASE_V1_28... Below that version they are absent from the schedule, and from_on_chain_gas_schedule leaves absent entries at their zeros() initial value rather than returning an error, so charge_batch_gas charges nothing. The batch path has no base or per-byte parameter to fall back on, unlike the non-batch path.

So a chain running a gas schedule below RELEASE_V1_28 accepts confidential transfers without charging for the dominant part of their verification cost. Nothing in the suite covered this, because MoveHarness builds its schedule at LATEST_GAS_FEATURE_VERSION, so the tests have only ever exercised the priced path.

RELEASE_V1_28 gates the 20 base_batch parameters and nothing else, so pinning one version below makes the whole delta attributable to batched range-proof verification.

What the profile shows

The test prints gas in the e2e harness's units, where gas_unit_scaling_factor is 1,000,000. Mainnet and testnet run at ~50,000, a 20x finer unit, so the right-hand column converts for scale. A transfer is 366 harness units, roughly 7,320 on mainnet.

share harness units mainnet equiv. what
52% 190 3,806 verify_batch_range_proof_internal x2
22% ~78 ~1,560 Move interpreter overhead
17% ~62 ~1,240 other ristretto255 natives
2% 7 140 intrinsic
<1% ~1 ~20 storage and io
366 ~7,320 total

Only the first row is exact: (120974478 + 69359728) internal gas, divided by 1,000,000 for the harness column and by 50,000 for the mainnet one. The middle rows are sums of individually-rounded line items and are approximate.

The interpreter share comes from instruction counts rather than expensive instructions: 20,092 copy_loc, 18,322 create_ty, 11,233 move_loc, 8,245 st_loc, 4,338 vec_push_back, all building and copying vectors of points and scalars. The other-natives share is dominated by 110 point_decompress and 93 point_compress calls, so points are round-tripped between representations rather than held in one.

Intrinsic cost is negligible, so transaction payload size is not a factor despite the size of the proofs.

Also

Extracts confidential_transfer_payload out of run_confidential_transfer so both tests can submit the same payload through run_raw and evaluate_gas_with_profiler. No behaviour change for existing callers.

Verification

cargo build -p e2e-move-tests --tests
RUST_MIN_STACK=33554432 cargo test -p e2e-move-tests --lib tests::confidential_asset:: -- --test-threads=4

All 16 tests in the module pass, the 14 pre-existing ones included:

test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 254 filtered out; finished in 88.11s

The version-gate test reports:

confidential_transfer gas: 366 at gas feature version 36, 176 at 31;
batch range-proof verification accounts for 190

190 is (120974478 + 69359728) / 1000000, matching the two parameters exactly.

RUST_MIN_STACK is needed to run this module at all; the pre-existing tests overflow the default stack in a debug build without it. The value above matches what CI sets in lint-test.yaml.

rustfmt and clippy are clean over the added code. The file carries pre-existing diffs from both on the base branch (lines 25, 66, 203), left untouched rather than mixed into this diff.

…on gated

The bulletproofs.verify.base_batch_* parameters only exist from RELEASE_V1_28.
Below that version parameter resolution leaves them at zero rather than failing,
so a confidential transfer is still accepted but pays nothing for verifying its
two batched range proofs.

Add a test that runs the same transfer at the current gas feature version and at
RELEASE_V1_27, asserting the transfer succeeds either way and that the gap
between the two is exactly the sum of the two parameters. RELEASE_V1_28 gates
only those parameters, so the delta is fully attributable to them.

Extract confidential_transfer_payload out of run_confidential_transfer so the
test can submit the same payload through run_raw and read the gas charged.
Breaks a transfer's gas down by operation using the gas profiler, asserting
that the two batched range proofs are charged exactly their parameters and
remain the largest single line item, and printing the rest so a shift in
composition is visible.

The breakdown shows range-proof verification at 52% of a transfer, other
ristretto255 natives at 17%, and Move interpreter overhead at 22%, the last of
which is driven by large instruction counts building and copying vectors of
points and scalars. Intrinsic and storage costs are negligible.
@ganymedio ganymedio changed the title Assert that batch range-proof gas for confidential transfers is version gated Add gas accounting tests for confidential transfers Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant