diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index cdcf670c640c6b3b1566fb20246c96956491e7a6..02814542158ddb65cfb382a6ae13881a7c1cbcbd 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -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, }; diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 8cb854ac97d939ac7825c742b284180e499d96b9..0062e2bbd4365a402cb1e3ac85df172db28275db 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -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))) diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 66ae8a4996ca49ff39b3f23b586658493659309e..b6a89281803f0291a1ac907afeb89e90c011fffe 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -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, } /// Can only be used for one call. @@ -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. @@ -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) + }, } } @@ -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)?; @@ -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)?; @@ -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) } @@ -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)?; @@ -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(()) }, @@ -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()); @@ -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); @@ -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, @@ -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);