The function gas_charge_inner is responsible for decreasing the available gas (context gas, stored in register cgas) and is used by both fixed and variable case. As can be seen the function profile determines the gas it tracks as the minimum of the gas costs and the context gas (let gas_use = gas.min(*cgas);). The profiler assumes that cgas has not been deducted from yet and thats why it needs to be invoked first before any deductions happen.
An example were this will go wrong:
assume cgas = 1000, cost = 900
after deduction cgas = 100
profiler tracks min(900, 100) = 100, but should have tracked the cost of 900
Impact Details
For contexts with large variable cost or relatively little context gas, the gas will be tracked incorrectly. While the effects are not immediate, incorrect profiling is likely to cause inaccurate adjustments of variable costs. That can either lead to a loss for the protocol or overcharging of users
References
Not applicable
Proof of concept
Proof of Concept
add the following test to profile_gas.rs:
#[test]
fn incorrect_gas_tracking() {
let rng = &mut StdRng::seed_from_u64(2322u64);
let gas_limit = 1_000;
let arb_fee_limit = 2_000;
let maturity = Default::default();
let height = Default::default();
// Deploy contract with loops
let reg_a = 0x20;
let reg_b = 0x21;
let script_code = vec![
op::movi(reg_a, 10),
op::movi(reg_b, 200000),
op::aloc(reg_a),
op::aloc(reg_a),
op::aloc(reg_b),
op::ret(RegId::ONE),
];
let tx_deploy =
TransactionBuilder::script(script_code.into_iter().collect(), vec![])
.max_fee_limit(arb_fee_limit)
.add_unsigned_coin_input(
SecretKey::random(rng),
rng.gen(),
arb_fee_limit,
Default::default(),
rng.gen(),
)
.script_gas_limit(gas_limit)
.maturity(maturity)
.finalize_checked(height);
let output = GasProfiler::default();
let mut vm = Interpreter::<_, _, _>::with_memory_storage();
vm.with_profiler(output.clone());
let mut client = MemoryClient::from_txtor(vm.into());
let receipts = client.transact(tx_deploy);
//print all receipts
for receipt in receipts.iter() {
println!("{:?}", receipt);
}
match output.data() {
Some(data) => {
let gas = data.gas();
for (key, value) in gas.iter() {
println!("{}: {}", key, value);
}
}
None => {
panic!("No gas data found");
}
}
}