diff --git a/prdoc/pr_5664.prdoc b/prdoc/pr_5664.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..7fbe15006da360aad837f958525feb628677d739
--- /dev/null
+++ b/prdoc/pr_5664.prdoc
@@ -0,0 +1,11 @@
+title: Calling an address without associated code is a balance transfer
+
+doc:
+  - audience: Runtime Dev
+    description: |
+     This makes pallet_revive behave like EVM where a balance transfer
+     is just a call to a plain wallet.
+
+crates:
+  - name: pallet-revive
+    bump: patch
diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs
index 25370459acb4fc654f99ec82d561e1c6c8f39746..d0d7c1bee2a5f9a65a226dd76f767ac44b463b3d 100644
--- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs
+++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs
@@ -34,6 +34,7 @@ pub extern "C" fn call() {
 	input!(
 		100,
 		callee_addr: &[u8; 20],
+		value: &[u8; 32],
 		input: [u8],
 	);
 
@@ -44,7 +45,7 @@ pub extern "C" fn call() {
 		0u64,                // How much ref_time to devote for the execution. 0 = all.
 		0u64,                // How much proof_size to devote for the execution. 0 = all.
 		None,                // No deposit limit.
-		&u256_bytes(100u64), // Value transferred to the contract.
+		value, 				 // Value transferred to the contract.
 		input,
 		None,
 	) {
diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs
index 468f5aa8240e8d796a85b71646878c23a7405e17..587d89b31984a49d14cf5b9303f112eb506a53b7 100644
--- a/substrate/frame/revive/src/exec.rs
+++ b/substrate/frame/revive/src/exec.rs
@@ -64,6 +64,8 @@ pub type ExecResult = Result<ExecReturnValue, ExecError>;
 /// Type for variable sized storage key. Used for transparent hashing.
 type VarSizedKey = BoundedVec<u8, ConstU32<{ limits::STORAGE_KEY_BYTES }>>;
 
+const FRAME_ALWAYS_EXISTS_ON_INSTANTIATE: &str = "The return value is only `None` if no contract exists at the specified address. This cannot happen on instantiate or delegate; qed";
+
 /// Combined key type for both fixed and variable sized storage keys.
 pub enum Key {
 	/// Variant for fixed sized keys.
@@ -591,26 +593,6 @@ enum CachedContract<T: Config> {
 	Terminated,
 }
 
-impl<T: Config> CachedContract<T> {
-	/// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise.
-	fn into_contract(self) -> Option<ContractInfo<T>> {
-		if let CachedContract::Cached(contract) = self {
-			Some(contract)
-		} else {
-			None
-		}
-	}
-
-	/// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise.
-	fn as_contract(&mut self) -> Option<&mut ContractInfo<T>> {
-		if let CachedContract::Cached(contract) = self {
-			Some(contract)
-		} else {
-			None
-		}
-	}
-}
-
 impl<T: Config> Frame<T> {
 	/// Return the `contract_info` of the current contract.
 	fn contract_info(&mut self) -> &mut ContractInfo<T> {
@@ -668,6 +650,24 @@ macro_rules! top_frame_mut {
 }
 
 impl<T: Config> CachedContract<T> {
+	/// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise.
+	fn into_contract(self) -> Option<ContractInfo<T>> {
+		if let CachedContract::Cached(contract) = self {
+			Some(contract)
+		} else {
+			None
+		}
+	}
+
+	/// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise.
+	fn as_contract(&mut self) -> Option<&mut ContractInfo<T>> {
+		if let CachedContract::Cached(contract) = self {
+			Some(contract)
+		} else {
+			None
+		}
+	}
+
 	/// Load the `contract_info` from storage if necessary.
 	fn load(&mut self, account_id: &T::AccountId) {
 		if let CachedContract::Invalidated = self {
@@ -717,20 +717,20 @@ where
 		value: BalanceOf<T>,
 		input_data: Vec<u8>,
 		debug_message: Option<&'a mut DebugBuffer>,
-	) -> Result<ExecReturnValue, ExecError> {
-		let (mut stack, executable) = Self::new(
-			FrameArgs::Call {
-				dest: T::AddressMapper::to_account_id(&dest),
-				cached_info: None,
-				delegated_call: None,
-			},
-			origin,
+	) -> ExecResult {
+		let dest = T::AddressMapper::to_account_id(&dest);
+		if let Some((mut stack, executable)) = Self::new(
+			FrameArgs::Call { dest: dest.clone(), cached_info: None, delegated_call: None },
+			origin.clone(),
 			gas_meter,
 			storage_meter,
 			value,
 			debug_message,
-		)?;
-		stack.run(executable, input_data)
+		)? {
+			stack.run(executable, input_data)
+		} else {
+			Self::transfer_no_contract(&origin, &dest, value)
+		}
 	}
 
 	/// Create and run a new call stack by instantiating a new contract.
@@ -765,7 +765,8 @@ where
 			storage_meter,
 			value,
 			debug_message,
-		)?;
+		)?
+		.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE);
 		let address = T::AddressMapper::to_address(&stack.top_frame().account_id);
 		stack.run(executable, input_data).map(|ret| (address, ret))
 	}
@@ -792,9 +793,13 @@ where
 			debug_message,
 		)
 		.unwrap()
+		.unwrap()
 	}
 
 	/// Create a new call stack.
+	///
+	/// Returns `None` when calling a non existant contract. This is not an error case
+	/// since this will result in a value transfer.
 	fn new(
 		args: FrameArgs<T, E>,
 		origin: Origin<T>,
@@ -802,8 +807,8 @@ where
 		storage_meter: &'a mut storage::meter::Meter<T>,
 		value: BalanceOf<T>,
 		debug_message: Option<&'a mut DebugBuffer>,
-	) -> Result<(Self, E), ExecError> {
-		let (first_frame, executable) = Self::new_frame(
+	) -> Result<Option<(Self, E)>, ExecError> {
+		let Some((first_frame, executable)) = Self::new_frame(
 			args,
 			value,
 			gas_meter,
@@ -811,7 +816,10 @@ where
 			storage_meter,
 			BalanceOf::<T>::zero(),
 			false,
-		)?;
+		)?
+		else {
+			return Ok(None);
+		};
 
 		let stack = Self {
 			origin,
@@ -826,7 +834,7 @@ where
 			_phantom: Default::default(),
 		};
 
-		Ok((stack, executable))
+		Ok(Some((stack, executable)))
 	}
 
 	/// Construct a new frame.
@@ -841,15 +849,20 @@ where
 		storage_meter: &mut storage::meter::GenericMeter<T, S>,
 		deposit_limit: BalanceOf<T>,
 		read_only: bool,
-	) -> Result<(Frame<T>, E), ExecError> {
+	) -> Result<Option<(Frame<T>, E)>, ExecError> {
 		let (account_id, contract_info, executable, delegate_caller, entry_point) = match frame_args
 		{
 			FrameArgs::Call { dest, cached_info, delegated_call } => {
 				let contract = if let Some(contract) = cached_info {
 					contract
 				} else {
-					<ContractInfoOf<T>>::get(T::AddressMapper::to_address(&dest))
-						.ok_or(<Error<T>>::ContractNotFound)?
+					if let Some(contract) =
+						<ContractInfoOf<T>>::get(T::AddressMapper::to_address(&dest))
+					{
+						contract
+					} else {
+						return Ok(None)
+					}
 				};
 
 				let (executable, delegate_caller) =
@@ -896,7 +909,7 @@ where
 			read_only,
 		};
 
-		Ok((frame, executable))
+		Ok(Some((frame, executable)))
 	}
 
 	/// Create a subsequent nested frame.
@@ -907,7 +920,7 @@ where
 		gas_limit: Weight,
 		deposit_limit: BalanceOf<T>,
 		read_only: bool,
-	) -> Result<E, ExecError> {
+	) -> Result<Option<E>, ExecError> {
 		if self.frames.len() as u32 == limits::CALL_STACK_DEPTH {
 			return Err(Error::<T>::MaxCallDepthReached.into());
 		}
@@ -929,7 +942,7 @@ where
 		let frame = top_frame_mut!(self);
 		let nested_gas = &mut frame.nested_gas;
 		let nested_storage = &mut frame.nested_storage;
-		let (frame, executable) = Self::new_frame(
+		if let Some((frame, executable)) = Self::new_frame(
 			frame_args,
 			value_transferred,
 			nested_gas,
@@ -937,15 +950,18 @@ where
 			nested_storage,
 			deposit_limit,
 			read_only,
-		)?;
-		self.frames.try_push(frame).map_err(|_| Error::<T>::MaxCallDepthReached)?;
-		Ok(executable)
+		)? {
+			self.frames.try_push(frame).map_err(|_| Error::<T>::MaxCallDepthReached)?;
+			Ok(Some(executable))
+		} else {
+			Ok(None)
+		}
 	}
 
 	/// Run the current (top) frame.
 	///
 	/// This can be either a call or an instantiate.
-	fn run(&mut self, executable: E, input_data: Vec<u8>) -> Result<ExecReturnValue, ExecError> {
+	fn run(&mut self, executable: E, input_data: Vec<u8>) -> ExecResult {
 		let frame = self.top_frame();
 		let entry_point = frame.entry_point;
 		let delegated_code_hash =
@@ -954,13 +970,15 @@ where
 		self.transient_storage.start_transaction();
 
 		let do_transaction = || {
+			let caller = self.caller();
+			let frame = top_frame_mut!(self);
+
 			// We need to charge the storage deposit before the initial transfer so that
 			// it can create the account in case the initial transfer is < ed.
 			if entry_point == ExportedFunction::Constructor {
 				// Root origin can't be used to instantiate a contract, so it is safe to assume that
 				// if we reached this point the origin has an associated account.
 				let origin = &self.origin.account_id()?;
-				let frame = top_frame_mut!(self);
 				frame.nested_storage.charge_instantiate(
 					origin,
 					&frame.account_id,
@@ -969,11 +987,15 @@ where
 				)?;
 				// Needs to be incremented before calling into the code so that it is visible
 				// in case of recursion.
-				<System<T>>::inc_account_nonce(self.caller().account_id()?);
+				<System<T>>::inc_account_nonce(caller.account_id()?);
 			}
 
 			// Every non delegate call or instantiate also optionally transfers the balance.
-			self.initial_transfer()?;
+			// If it is a delegate call, then we've already transferred tokens in the
+			// last non-delegate frame.
+			if delegated_code_hash.is_none() {
+				Self::transfer_from_origin(&caller, &frame.account_id, frame.value_transferred)?;
+			}
 
 			let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id);
 
@@ -1166,40 +1188,40 @@ where
 	}
 
 	/// Transfer some funds from `from` to `to`.
-	fn transfer(
-		preservation: Preservation,
-		from: &T::AccountId,
-		to: &T::AccountId,
-		value: BalanceOf<T>,
-	) -> DispatchResult {
-		if !value.is_zero() && from != to {
-			T::Currency::transfer(from, to, value, preservation)
+	fn transfer(from: &T::AccountId, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult {
+		// this avoids events to be emitted for zero balance transfers
+		if !value.is_zero() {
+			T::Currency::transfer(from, to, value, Preservation::Preserve)
 				.map_err(|_| Error::<T>::TransferFailed)?;
 		}
 		Ok(())
 	}
 
-	// The transfer as performed by a call or instantiate.
-	fn initial_transfer(&self) -> DispatchResult {
-		let frame = self.top_frame();
-
-		// If it is a delegate call, then we've already transferred tokens in the
-		// last non-delegate frame.
-		if frame.delegate_caller.is_some() {
-			return Ok(());
-		}
-
-		let value = frame.value_transferred;
-
-		// Get the account id from the caller.
-		// If the caller is root there is no account to transfer from, and therefore we can't take
-		// any `value` other than 0.
-		let caller = match self.caller() {
+	/// Same as `transfer` but `from` is an `Origin`.
+	fn transfer_from_origin(
+		from: &Origin<T>,
+		to: &T::AccountId,
+		value: BalanceOf<T>,
+	) -> DispatchResult {
+		// If the from address is root there is no account to transfer from, and therefore we can't
+		// take any `value` other than 0.
+		let from = match from {
 			Origin::Signed(caller) => caller,
 			Origin::Root if value.is_zero() => return Ok(()),
 			Origin::Root => return DispatchError::RootNotAllowed.into(),
 		};
-		Self::transfer(Preservation::Preserve, &caller, &frame.account_id, value)
+		Self::transfer(from, to, value)
+	}
+
+	/// Same as `transfer_from_origin` but creates an `ExecReturnValue` on success.
+	fn transfer_no_contract(
+		from: &Origin<T>,
+		to: &T::AccountId,
+		value: BalanceOf<T>,
+	) -> ExecResult {
+		Self::transfer_from_origin(from, to, value)
+			.map(|_| ExecReturnValue::default())
+			.map_err(Into::into)
 	}
 
 	/// Reference to the current (top) frame.
@@ -1255,13 +1277,14 @@ where
 		input_data: Vec<u8>,
 		allows_reentry: bool,
 		read_only: bool,
-	) -> Result<ExecReturnValue, ExecError> {
+	) -> ExecResult {
 		// Before pushing the new frame: Protect the caller contract against reentrancy attacks.
 		// It is important to do this before calling `allows_reentry` so that a direct recursion
 		// is caught by it.
 		self.top_frame_mut().allows_reentry = allows_reentry;
 
 		let dest = T::AddressMapper::to_account_id(dest);
+		let value = value.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?;
 
 		let try_call = || {
 			if !self.allows_reentry(&dest) {
@@ -1278,15 +1301,22 @@ where
 					CachedContract::Cached(contract) => Some(contract.clone()),
 					_ => None,
 				});
-			let executable = self.push_frame(
-				FrameArgs::Call { dest, cached_info, delegated_call: None },
-				value.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?,
+			if let Some(executable) = self.push_frame(
+				FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None },
+				value,
 				gas_limit,
 				deposit_limit.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?,
 				// Enable read-only access if requested; cannot disable it if already set.
 				read_only || self.is_read_only(),
-			)?;
-			self.run(executable, input_data)
+			)? {
+				self.run(executable, input_data)
+			} else {
+				Self::transfer_no_contract(
+					&Origin::from_account_id(self.account_id().clone()),
+					&dest,
+					value,
+				)
+			}
 		};
 
 		// We need to make sure to reset `allows_reentry` even on failure.
@@ -1319,7 +1349,7 @@ where
 			BalanceOf::<T>::zero(),
 			self.is_read_only(),
 		)?;
-		self.run(executable, input_data)
+		self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data)
 	}
 
 	fn instantiate(
@@ -1346,7 +1376,8 @@ where
 			self.is_read_only(),
 		)?;
 		let address = T::AddressMapper::to_address(&self.top_frame().account_id);
-		self.run(executable, input_data).map(|ret| (address, ret))
+		self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data)
+			.map(|ret| (address, ret))
 	}
 
 	fn terminate(&mut self, beneficiary: &H160) -> DispatchResult {
@@ -1379,7 +1410,6 @@ where
 
 	fn transfer(&mut self, to: &H160, value: U256) -> DispatchResult {
 		Self::transfer(
-			Preservation::Preserve,
 			&self.top_frame().account_id,
 			&T::AddressMapper::to_account_id(to),
 			value.try_into().map_err(|_| Error::<T>::BalanceConversionFailed)?,
@@ -1854,7 +1884,7 @@ mod tests {
 			set_balance(&ALICE, 100);
 			set_balance(&BOB, 0);
 
-			MockStack::transfer(Preservation::Preserve, &ALICE, &BOB, 55).unwrap();
+			MockStack::transfer(&ALICE, &BOB, 55).unwrap();
 
 			assert_eq!(get_balance(&ALICE), 45);
 			assert_eq!(get_balance(&BOB), 55);
@@ -1974,7 +2004,7 @@ mod tests {
 		ExtBuilder::default().build().execute_with(|| {
 			set_balance(&origin, 0);
 
-			let result = MockStack::transfer(Preservation::Preserve, &origin, &dest, 100);
+			let result = MockStack::transfer(&origin, &dest, 100);
 
 			assert_eq!(result, Err(Error::<Test>::TransferFailed.into()));
 			assert_eq!(get_balance(&origin), 0);
@@ -2848,15 +2878,35 @@ mod tests {
 	}
 
 	#[test]
-	fn recursive_call_during_constructor_fails() {
+	fn recursive_call_during_constructor_is_balance_transfer() {
 		let code = MockLoader::insert(Constructor, |ctx, _| {
 			let account_id = ctx.ext.account_id().clone();
 			let addr = <Test as Config>::AddressMapper::to_address(&account_id);
+			let balance = ctx.ext.balance();
 
-			assert_matches!(
-						   ctx.ext.call(Weight::zero(), U256::zero(), &addr, U256::zero(), vec![],
-			true, false), 				Err(ExecError{error, ..}) if error == <Error<Test>>::ContractNotFound.into()
-					   );
+			// Calling ourselves during the constructor will trigger a balance
+			// transfer since no contract exist yet.
+			assert_ok!(ctx.ext.call(
+				Weight::zero(),
+				U256::zero(),
+				&addr,
+				(balance - 1).into(),
+				vec![],
+				true,
+				false
+			));
+
+			// Should also work with call data set as it is ignored when no
+			// contract is deployed.
+			assert_ok!(ctx.ext.call(
+				Weight::zero(),
+				U256::zero(),
+				&addr,
+				1u32.into(),
+				vec![1, 2, 3, 4],
+				true,
+				false
+			));
 			exec_success()
 		});
 
@@ -2879,7 +2929,7 @@ mod tests {
 					executable,
 					&mut gas_meter,
 					&mut storage_meter,
-					min_balance,
+					10,
 					vec![],
 					Some(&[0; 32]),
 					None,
@@ -2888,6 +2938,51 @@ mod tests {
 			});
 	}
 
+	#[test]
+	fn cannot_send_more_balance_than_available_to_self() {
+		let code_hash = MockLoader::insert(Call, |ctx, _| {
+			let account_id = ctx.ext.account_id().clone();
+			let addr = <Test as Config>::AddressMapper::to_address(&account_id);
+			let balance = ctx.ext.balance();
+
+			assert_err!(
+				ctx.ext.call(
+					Weight::zero(),
+					U256::zero(),
+					&addr,
+					(balance + 1).into(),
+					vec![],
+					true,
+					false
+				),
+				<Error<Test>>::TransferFailed
+			);
+			exec_success()
+		});
+
+		ExtBuilder::default()
+			.with_code_hashes(MockLoader::code_hashes())
+			.build()
+			.execute_with(|| {
+				let min_balance = <Test as Config>::Currency::minimum_balance();
+				let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+				set_balance(&ALICE, min_balance * 10);
+				place_contract(&BOB, code_hash);
+				let origin = Origin::from_account_id(ALICE);
+				let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap();
+				MockStack::run_call(
+					origin,
+					BOB_ADDR,
+					&mut gas_meter,
+					&mut storage_meter,
+					0,
+					vec![],
+					None,
+				)
+				.unwrap();
+			});
+	}
+
 	#[test]
 	fn printing_works() {
 		let code_hash = MockLoader::insert(Call, |ctx, _| {
diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs
index 1b48527d23d7fdf3080f16117203f8f17fa28636..67bc144c3dd233924928695afdbe5a5057ce46a9 100644
--- a/substrate/frame/revive/src/primitives.rs
+++ b/substrate/frame/revive/src/primitives.rs
@@ -106,7 +106,7 @@ pub enum ContractAccessError {
 }
 
 /// Output of a contract call or instantiation which ran to completion.
-#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
+#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Default)]
 pub struct ExecReturnValue {
 	/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
 	pub flags: ReturnFlags,
diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs
index 73914c9aae0730f62bd0c4c1368f2fba64cbcfa0..19d6eabd577bdd518f5a839702ec1b1185046f12 100644
--- a/substrate/frame/revive/src/tests.rs
+++ b/substrate/frame/revive/src/tests.rs
@@ -50,7 +50,6 @@ use codec::{Decode, Encode};
 use frame_support::{
 	assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok,
 	derive_impl,
-	dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo},
 	pallet_prelude::EnsureOrigin,
 	parameter_types,
 	storage::child,
@@ -159,6 +158,12 @@ pub mod test_utils {
 		// Assert that contract code is stored, and get its size.
 		PristineCode::<Test>::try_get(&code_hash).unwrap().len()
 	}
+	pub fn u256_bytes(u: u64) -> [u8; 32] {
+		let mut buffer = [0u8; 32];
+		let bytes = u.to_le_bytes();
+		buffer[..8].copy_from_slice(&bytes);
+		buffer
+	}
 }
 
 mod builder {
@@ -589,25 +594,15 @@ mod run_tests {
 	use pretty_assertions::{assert_eq, assert_ne};
 	use sp_core::U256;
 
-	// Perform a call to a plain account.
-	// The actual transfer fails because we can only call contracts.
-	// Then we check that at least the base costs where charged (no runtime gas costs.)
 	#[test]
-	fn calling_plain_account_fails() {
+	fn calling_plain_account_is_balance_transfer() {
 		ExtBuilder::default().build().execute_with(|| {
 			let _ = <Test as Config>::Currency::set_balance(&ALICE, 100_000_000);
-			let base_cost = <<Test as Config>::WeightInfo as WeightInfo>::call();
-
-			assert_eq!(
-				builder::call(BOB_ADDR).build(),
-				Err(DispatchErrorWithPostInfo {
-					error: Error::<Test>::ContractNotFound.into(),
-					post_info: PostDispatchInfo {
-						actual_weight: Some(base_cost),
-						pays_fee: Default::default(),
-					},
-				})
-			);
+			assert!(!<ContractInfoOf<Test>>::contains_key(BOB_ADDR));
+			assert_eq!(test_utils::get_balance(&BOB_CONTRACT_ID), 0);
+			let result = builder::bare_call(BOB_ADDR).value(42).build_and_unwrap_result();
+			assert_eq!(test_utils::get_balance(&BOB_CONTRACT_ID), 42);
+			assert_eq!(result, Default::default());
 		});
 	}
 
@@ -1472,6 +1467,8 @@ mod run_tests {
 
 	#[test]
 	fn call_return_code() {
+		use test_utils::u256_bytes;
+
 		let (caller_code, _caller_hash) = compile_module("call_return_code").unwrap();
 		let (callee_code, _callee_hash) = compile_module("ok_trap_revert").unwrap();
 		ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
@@ -1481,28 +1478,60 @@ mod run_tests {
 
 			let bob = builder::bare_instantiate(Code::Upload(caller_code))
 				.value(min_balance * 100)
-				.data(vec![0])
 				.build_and_unwrap_contract();
-			<Test as Config>::Currency::set_balance(&bob.account_id, min_balance);
 
 			// Contract calls into Django which is no valid contract
+			// This will be a balance transfer into a new account
+			// with more than the contract has which will make the transfer fail
+			let result = builder::bare_call(bob.addr)
+				.data(
+					AsRef::<[u8]>::as_ref(&DJANGO_ADDR)
+						.iter()
+						.chain(&u256_bytes(min_balance * 200))
+						.cloned()
+						.collect(),
+				)
+				.build_and_unwrap_result();
+			assert_return_code!(result, RuntimeReturnCode::TransferFailed);
+
+			// Sending less than the minimum balance will also make the transfer fail
 			let result = builder::bare_call(bob.addr)
-				.data(AsRef::<[u8]>::as_ref(&DJANGO).to_vec())
+				.data(
+					AsRef::<[u8]>::as_ref(&DJANGO_ADDR)
+						.iter()
+						.chain(&u256_bytes(42))
+						.cloned()
+						.collect(),
+				)
 				.build_and_unwrap_result();
-			assert_return_code!(result, RuntimeReturnCode::NotCallable);
+			assert_return_code!(result, RuntimeReturnCode::TransferFailed);
+
+			// Sending at least the minimum balance should result in success but
+			// no code called.
+			assert_eq!(test_utils::get_balance(&ETH_DJANGO), 0);
+			let result = builder::bare_call(bob.addr)
+				.data(
+					AsRef::<[u8]>::as_ref(&DJANGO_ADDR)
+						.iter()
+						.chain(&u256_bytes(55))
+						.cloned()
+						.collect(),
+				)
+				.build_and_unwrap_result();
+			assert_return_code!(result, RuntimeReturnCode::Success);
+			assert_eq!(test_utils::get_balance(&ETH_DJANGO), 55);
 
 			let django = builder::bare_instantiate(Code::Upload(callee_code))
 				.origin(RuntimeOrigin::signed(CHARLIE))
 				.value(min_balance * 100)
-				.data(vec![0])
 				.build_and_unwrap_contract();
-			<Test as Config>::Currency::set_balance(&django.account_id, min_balance);
 
-			// Contract has only the minimal balance so any transfer will fail.
+			// Sending more than the contract has will make the transfer fail.
 			let result = builder::bare_call(bob.addr)
 				.data(
 					AsRef::<[u8]>::as_ref(&django.addr)
 						.iter()
+						.chain(&u256_bytes(min_balance * 300))
 						.chain(&0u32.to_le_bytes())
 						.cloned()
 						.collect(),
@@ -1516,6 +1545,7 @@ mod run_tests {
 				.data(
 					AsRef::<[u8]>::as_ref(&django.addr)
 						.iter()
+						.chain(&u256_bytes(5))
 						.chain(&1u32.to_le_bytes())
 						.cloned()
 						.collect(),
@@ -1528,6 +1558,7 @@ mod run_tests {
 				.data(
 					AsRef::<[u8]>::as_ref(&django.addr)
 						.iter()
+						.chain(&u256_bytes(5))
 						.chain(&2u32.to_le_bytes())
 						.cloned()
 						.collect(),
diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs
index 528b0ababfa01f5e5ca28d2198ee182c031ed2fb..d9257d38b66ea864103df4643512f4843952d400 100644
--- a/substrate/frame/revive/src/wasm/runtime.rs
+++ b/substrate/frame/revive/src/wasm/runtime.rs
@@ -581,8 +581,7 @@ impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> {
 							None => Some(Err(Error::<E::T>::InvalidCallFlags.into())),
 							Some(flags) => Some(Ok(ExecReturnValue { flags, data })),
 						},
-					Err(TrapReason::Termination) =>
-						Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })),
+					Err(TrapReason::Termination) => Some(Ok(Default::default())),
 					Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())),
 				}
 			},
diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs
index 17b91ce2b3be8225924dd6ddfe908ad9592a381a..763a89d6c030489b3867417357c8310cbfa686f3 100644
--- a/substrate/frame/revive/uapi/src/flags.rs
+++ b/substrate/frame/revive/uapi/src/flags.rs
@@ -20,6 +20,7 @@ use bitflags::bitflags;
 bitflags! {
 	/// Flags used by a contract to customize exit behaviour.
 	#[cfg_attr(feature = "scale", derive(codec::Encode, codec::Decode, scale_info::TypeInfo))]
+	#[derive(Default)]
 	pub struct ReturnFlags: u32 {
 		/// If this bit is set all changes made by the contract execution are rolled back.
 		const REVERT = 0x0000_0001;