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);