Skip to content

Commit c604fd8

Browse files
committed
revive: add tests for substrate_execution nested metering
Add regression tests for the gas limit cap fix in substrate_execution. These tests validate that nested calls requesting all gas receive meaningful weight allocation when using large deposit limits. Note: The tests use Balance = u64, so the bug doesn't fully manifest. The fix is critical for u128 production configs where deposit_left can exceed u64::MAX.
1 parent 668fb4d commit c604fd8

File tree

2 files changed

+83
-4
lines changed

2 files changed

+83
-4
lines changed

substrate/frame/revive/src/metering/math.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,8 @@ pub mod substrate_execution {
120120
return Err(<Error<T>>::OutOfGas.into());
121121
};
122122

123-
// Cap remaining_gas to u64::MAX since Ethereum gas is a u64 value.
124-
// Without this cap, when deposit_left is very large (e.g., u128::MAX),
125-
// the ratio calculation would produce a near-zero value, causing the
126-
// nested call to receive almost no weight even when requesting all gas.
123+
// Cap to u64::MAX since Ethereum gas is u64. Without this, large deposit_left
124+
// (e.g., u128::MAX) causes ratio ≈ 0, giving nested calls almost no weight.
127125
let remaining_gas = remaining_gas.min(u64::MAX.saturated_into());
128126

129127
let gas_limit = remaining_gas.min(*gas);

substrate/frame/revive/src/metering/tests.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,3 +753,84 @@ fn catch_constructor_test() {
753753
assert_eq!("revert: invalid address", gas_trace.calls[0].revert_reason.as_ref().unwrap());
754754
});
755755
}
756+
757+
/// Regression test for proxy contract delegatecall with large deposit limits.
758+
///
759+
/// When deposit_left is very large (u128::MAX in production), remaining_gas becomes huge,
760+
/// causing ratio = gas_limit / remaining_gas ≈ 0. This resulted in nested calls receiving
761+
/// almost no weight. The fix caps remaining_gas to u64::MAX since Ethereum gas is u64.
762+
///
763+
/// Note: This test uses Balance = u64, so the bug doesn't fully manifest here.
764+
/// The fix is a no-op in u64 configs but critical for u128 production configs.
765+
#[test]
766+
fn substrate_nesting_with_large_deposit_and_max_gas_request() {
767+
use super::math::substrate_execution;
768+
769+
ExtBuilder::default()
770+
.with_next_fee_multiplier(FixedU128::from_rational(1, 5))
771+
.build()
772+
.execute_with(|| {
773+
let weight_limit = Weight::from_parts(1_000_000_000, 10_000);
774+
let deposit_limit = u64::MAX;
775+
776+
let mut root_meter =
777+
substrate_execution::new_root::<Test>(weight_limit, deposit_limit).unwrap();
778+
779+
root_meter.charge_weight_token(TestToken(1000, 100)).unwrap();
780+
root_meter.charge_deposit(&StorageDeposit::Charge(1000)).unwrap();
781+
782+
let weight_left_before = root_meter.weight_left().unwrap();
783+
784+
let gas_scale: u64 = <Test as Config>::GasScale::get().into();
785+
let max_eth_gas = u64::MAX / gas_scale;
786+
787+
let nested = root_meter
788+
.new_nested(&CallResources::Ethereum { gas: max_eth_gas, add_stipend: false })
789+
.unwrap();
790+
791+
let nested_weight_left = nested.weight_left().unwrap();
792+
793+
assert!(
794+
nested_weight_left.ref_time() >= weight_left_before.ref_time() / 2,
795+
"Nested meter should get at least 50% of remaining weight. \
796+
Got ref_time: {}, expected at least: {}",
797+
nested_weight_left.ref_time(),
798+
weight_left_before.ref_time() / 2
799+
);
800+
801+
assert!(nested.deposit_left().unwrap() > 0);
802+
});
803+
}
804+
805+
/// Test ratio-based weight scaling for partial gas requests in substrate execution.
806+
#[test]
807+
fn substrate_nesting_with_partial_gas_request_scales_weight() {
808+
use super::math::substrate_execution;
809+
810+
ExtBuilder::default()
811+
.with_next_fee_multiplier(FixedU128::from_rational(1, 5))
812+
.build()
813+
.execute_with(|| {
814+
let weight_limit = Weight::from_parts(1_000_000_000, 10_000);
815+
let deposit_limit = 1_000_000_000u64;
816+
817+
let mut root_meter =
818+
substrate_execution::new_root::<Test>(weight_limit, deposit_limit).unwrap();
819+
820+
root_meter.charge_weight_token(TestToken(1000, 100)).unwrap();
821+
822+
let weight_left_before = root_meter.weight_left().unwrap();
823+
824+
let gas_scale: u64 = <Test as Config>::GasScale::get().into();
825+
let partial_gas = (u64::MAX / gas_scale) / 10;
826+
827+
let nested = root_meter
828+
.new_nested(&CallResources::Ethereum { gas: partial_gas, add_stipend: false })
829+
.unwrap();
830+
831+
let nested_weight_left = nested.weight_left().unwrap();
832+
833+
assert!(nested_weight_left.ref_time() > 0);
834+
assert!(nested_weight_left.ref_time() <= weight_left_before.ref_time());
835+
});
836+
}

0 commit comments

Comments
 (0)