Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Allow to distinguish out of gas from other traps #4883

Merged
merged 3 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 218,
spec_version: 219,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
};
Expand Down
45 changes: 45 additions & 0 deletions frame/contracts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,51 @@ fn dispatch_call_not_dispatched_after_top_level_transaction_failure() {
});
}

const CODE_RUN_OUT_OF_GAS: &str = r#"
(module
(func (export "call")
(loop $inf (br $inf)) ;; just run out of gas
(unreachable)
)
(func (export "deploy"))
)
"#;

#[test]
fn run_out_of_gas() {
let (wasm, code_hash) = compile_module::<Test>(CODE_RUN_OUT_OF_GAS).unwrap();

ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
Balances::deposit_creating(&ALICE, 1_000_000);

assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));

assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
100_000,
code_hash.into(),
vec![],
));

// Call the contract with a fixed gas limit. It must run out of gas because it just
// loops forever.
assert_err!(
Contracts::call(
Origin::signed(ALICE),
BOB, // newly created account
0,
1000,
vec![],
),
"ran out of gas during contract execution"
);
});
}

const CODE_SET_RENT: &str = r#"
(module
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
Expand Down
78 changes: 67 additions & 11 deletions frame/contracts/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const TRAP_RETURN_CODE: u32 = 0x0100;
enum SpecialTrap {
/// Signals that trap was generated in response to call `ext_return` host function.
Return(Vec<u8>),
/// Signals that trap was generated because the contract exhausted its gas limit.
OutOfGas,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful to leave some words about this variant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Makes sense.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment

}

/// Can only be used for one call.
Expand Down Expand Up @@ -74,9 +76,21 @@ pub(crate) fn to_execution_result<E: Ext>(
runtime: Runtime<E>,
sandbox_result: Result<sp_sandbox::ReturnValue, sp_sandbox::Error>,
) -> ExecResult {
// Special case. The trap was the result of the execution `return` host function.
if let Some(SpecialTrap::Return(data)) = runtime.special_trap {
return Ok(ExecReturnValue { status: STATUS_SUCCESS, data });
match runtime.special_trap {
// The trap was the result of the execution `return` host function.
Some(SpecialTrap::Return(data)) => {
return Ok(ExecReturnValue {
status: STATUS_SUCCESS,
data,
})
}
Some(SpecialTrap::OutOfGas) => {
return Err(ExecError {
reason: "ran out of gas during contract execution".into(),
buffer: runtime.scratch_buf,
})
}
_ => (),
}

// Check the exact type of the error.
Expand Down Expand Up @@ -179,11 +193,15 @@ impl<T: Trait> Token<T> for RuntimeToken {
fn charge_gas<T: Trait, Tok: Token<T>>(
gas_meter: &mut GasMeter<T>,
metadata: &Tok::Metadata,
special_trap: &mut Option<SpecialTrap>,
token: Tok,
) -> Result<(), sp_sandbox::HostError> {
match gas_meter.charge(metadata, token) {
GasMeterResult::Proceed => Ok(()),
GasMeterResult::OutOfGas => Err(sp_sandbox::HostError),
GasMeterResult::OutOfGas => {
*special_trap = Some(SpecialTrap::OutOfGas);
Err(sp_sandbox::HostError)
},
}
}

Expand All @@ -200,7 +218,12 @@ fn read_sandbox_memory<E: Ext>(
ptr: u32,
len: u32,
) -> Result<Vec<u8>, sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(len),
)?;

let mut buf = vec![0u8; len as usize];
ctx.memory.get(ptr, buf.as_mut_slice()).map_err(|_| sp_sandbox::HostError)?;
Expand All @@ -220,7 +243,12 @@ fn read_sandbox_memory_into_scratch<E: Ext>(
ptr: u32,
len: u32,
) -> Result<(), sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(len),
)?;

ctx.scratch_buf.resize(len as usize, 0);
ctx.memory.get(ptr, ctx.scratch_buf.as_mut_slice()).map_err(|_| sp_sandbox::HostError)?;
Expand All @@ -240,7 +268,12 @@ fn read_sandbox_memory_into_buf<E: Ext>(
ptr: u32,
buf: &mut [u8],
) -> Result<(), sp_sandbox::HostError> {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReadMemory(buf.len() as u32))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReadMemory(buf.len() as u32),
)?;

ctx.memory.get(ptr, buf).map_err(Into::into)
}
Expand Down Expand Up @@ -273,12 +306,18 @@ fn read_sandbox_memory_as<E: Ext, D: Decode>(
/// - designated area is not within the bounds of the sandbox memory.
fn write_sandbox_memory<T: Trait>(
schedule: &Schedule,
special_trap: &mut Option<SpecialTrap>,
gas_meter: &mut GasMeter<T>,
memory: &sp_sandbox::Memory,
ptr: u32,
buf: &[u8],
) -> Result<(), sp_sandbox::HostError> {
charge_gas(gas_meter, schedule, RuntimeToken::WriteMemory(buf.len() as u32))?;
charge_gas(
gas_meter,
schedule,
special_trap,
RuntimeToken::WriteMemory(buf.len() as u32),
)?;

memory.set(ptr, buf)?;

Expand All @@ -300,7 +339,12 @@ define_env!(Env, <E: Ext>,
//
// - amount: How much gas is used.
gas(ctx, amount: u32) => {
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::Explicit(amount))?;
charge_gas(
&mut ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::Explicit(amount)
)?;
Ok(())
},

Expand Down Expand Up @@ -520,7 +564,12 @@ define_env!(Env, <E: Ext>,
//
// This is the only way to return a data buffer to the caller.
ext_return(ctx, data_ptr: u32, data_len: u32) => {
charge_gas(ctx.gas_meter, ctx.schedule, RuntimeToken::ReturnData(data_len))?;
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ReturnData(data_len)
)?;

read_sandbox_memory_into_scratch(ctx, data_ptr, data_len)?;
let output_buf = mem::replace(&mut ctx.scratch_buf, Vec::new());
Expand Down Expand Up @@ -652,7 +701,12 @@ define_env!(Env, <E: Ext>,
let balance_fee = <<E as Ext>::T as Trait>::ComputeDispatchFee::compute_dispatch_fee(&call);
approx_gas_for_balance(ctx.gas_meter.gas_price(), balance_fee)
};
charge_gas(&mut ctx.gas_meter, ctx.schedule, RuntimeToken::ComputedDispatchFee(fee))?;
charge_gas(
&mut ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::ComputedDispatchFee(fee)
)?;

ctx.ext.note_dispatch_call(call);

Expand Down Expand Up @@ -756,6 +810,7 @@ define_env!(Env, <E: Ext>,
// Finally, perform the write.
write_sandbox_memory(
ctx.schedule,
&mut ctx.special_trap,
ctx.gas_meter,
&ctx.memory,
dest_ptr,
Expand Down Expand Up @@ -803,6 +858,7 @@ define_env!(Env, <E: Ext>,
charge_gas(
ctx.gas_meter,
ctx.schedule,
&mut ctx.special_trap,
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
)?;
ctx.ext.deposit_event(topics, event_data);
Expand Down