From 234749e0854c86885b3a9358c0c4513dfe9607f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= <alex.theissen@me.com>
Date: Tue, 6 Dec 2022 12:01:58 +0100
Subject: [PATCH] contracts: Add `instantiation_nonce` API (#12800)

* Add `instantiation_nonce` API

* Fixes for tests

* Update frame/contracts/src/schedule.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
Co-authored-by: command-bot <>
---
 frame/contracts/proc-macro/src/lib.rs   |  4 ++
 frame/contracts/src/benchmarking/mod.rs | 20 +++++++
 frame/contracts/src/exec.rs             | 71 +++++++++++++++++++++----
 frame/contracts/src/schedule.rs         |  8 ++-
 frame/contracts/src/wasm/mod.rs         | 41 +++++++++++---
 frame/contracts/src/wasm/runtime.rs     | 13 +++++
 frame/contracts/src/weights.rs          | 31 +++++++++++
 7 files changed, 169 insertions(+), 19 deletions(-)

diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs
index 82b5b728a73ee..a8f95bd10cff8 100644
--- a/frame/contracts/proc-macro/src/lib.rs
+++ b/frame/contracts/proc-macro/src/lib.rs
@@ -163,6 +163,7 @@ struct HostFn {
 enum HostFnReturn {
 	Unit,
 	U32,
+	U64,
 	ReturnCode,
 }
 
@@ -171,6 +172,7 @@ impl HostFnReturn {
 		let ok = match self {
 			Self::Unit => quote! { () },
 			Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 },
+			Self::U64 => quote! { ::core::primitive::u64 },
 		};
 		quote! {
 			::core::result::Result<#ok, ::wasmi::core::Trap>
@@ -241,6 +243,7 @@ impl HostFn {
 		let msg = r#"Should return one of the following:
 				- Result<(), TrapReason>,
 				- Result<ReturnCode, TrapReason>,
+				- Result<u64, TrapReason>,
 				- Result<u32, TrapReason>"#;
 		let ret_ty = match item.clone().sig.output {
 			syn::ReturnType::Type(_, ty) => Ok(ty.clone()),
@@ -303,6 +306,7 @@ impl HostFn {
 						let returns = match ok_ty_str.as_str() {
 							"()" => Ok(HostFnReturn::Unit),
 							"u32" => Ok(HostFnReturn::U32),
+							"u64" => Ok(HostFnReturn::U64),
 							"ReturnCode" => Ok(HostFnReturn::ReturnCode),
 							_ => Err(err(arg1.span(), &msg)),
 						}?;
diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs
index 87e2ca4388784..6b8701ac84d96 100644
--- a/frame/contracts/src/benchmarking/mod.rs
+++ b/frame/contracts/src/benchmarking/mod.rs
@@ -2138,6 +2138,26 @@ benchmarks! {
 		let origin = RawOrigin::Signed(instance.caller.clone());
 	}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
 
+	seal_instantiation_nonce {
+		let r in 0 .. API_BENCHMARK_BATCHES;
+		let code = WasmModule::<T>::from(ModuleDefinition {
+			memory: Some(ImportedMemory::max::<T>()),
+			imported_functions: vec![ImportedFunction {
+				module: "seal0",
+				name: "instantiation_nonce",
+				params: vec![],
+				return_type: Some(ValueType::I64),
+			}],
+			call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
+				Instruction::Call(0),
+				Instruction::Drop,
+			])),
+			.. Default::default()
+		});
+		let instance = Contract::<T>::new(code, vec![])?;
+		let origin = RawOrigin::Signed(instance.caller.clone());
+	}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
+
 	// We make the assumption that pushing a constant and dropping a value takes roughly
 	// the same amount of time. We follow that `t.load` and `drop` both have the weight
 	// of this benchmark / 2. We need to make this assumption because there is no way
diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs
index 2884779d8fda7..c0cf6a9f4c4c4 100644
--- a/frame/contracts/src/exec.rs
+++ b/frame/contracts/src/exec.rs
@@ -305,6 +305,9 @@ pub trait Ext: sealing::Sealed {
 	/// are not calculated as separate entrance.
 	/// A value of 0 means it does not exist on the call stack.
 	fn account_reentrance_count(&self, account_id: &AccountIdOf<Self::T>) -> u32;
+
+	/// Returns a nonce that is incremented for every instantiated contract.
+	fn nonce(&mut self) -> u64;
 }
 
 /// Describes the different functions that can be exported by an [`Executable`].
@@ -654,7 +657,7 @@ where
 		let (mut stack, executable) = Self::new(
 			FrameArgs::Instantiate {
 				sender: origin.clone(),
-				nonce: Self::initial_nonce(),
+				nonce: <Nonce<T>>::get().wrapping_add(1),
 				executable,
 				salt,
 			},
@@ -1068,19 +1071,10 @@ where
 
 	/// Increments and returns the next nonce. Pulls it from storage if it isn't in cache.
 	fn next_nonce(&mut self) -> u64 {
-		let next = if let Some(current) = self.nonce {
-			current.wrapping_add(1)
-		} else {
-			Self::initial_nonce()
-		};
+		let next = self.nonce().wrapping_add(1);
 		self.nonce = Some(next);
 		next
 	}
-
-	/// Pull the current nonce from storage.
-	fn initial_nonce() -> u64 {
-		<Nonce<T>>::get().wrapping_add(1)
-	}
 }
 
 impl<'a, T, E> Ext for Stack<'a, T, E>
@@ -1394,6 +1388,16 @@ where
 			.filter(|f| f.delegate_caller.is_none() && &f.account_id == account_id)
 			.count() as u32
 	}
+
+	fn nonce(&mut self) -> u64 {
+		if let Some(current) = self.nonce {
+			current
+		} else {
+			let current = <Nonce<T>>::get();
+			self.nonce = Some(current);
+			current
+		}
+	}
 }
 
 mod sealing {
@@ -3325,4 +3329,49 @@ mod tests {
 			assert_matches!(result, Ok(_));
 		});
 	}
+
+	#[test]
+	fn nonce_api_works() {
+		let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped());
+		let success_code = MockLoader::insert(Constructor, |_, _| exec_success());
+		let code_hash = MockLoader::insert(Call, move |ctx, _| {
+			// It is set to one when this contract was instantiated by `place_contract`
+			assert_eq!(ctx.ext.nonce(), 1);
+			// Should not change without any instantation in-between
+			assert_eq!(ctx.ext.nonce(), 1);
+			// Should not change with a failed instantiation
+			assert_err!(
+				ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],),
+				ExecError {
+					error: <Error<Test>>::ContractTrapped.into(),
+					origin: ErrorOrigin::Callee
+				}
+			);
+			assert_eq!(ctx.ext.nonce(), 1);
+			// Successful instantation increments
+			ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap();
+			assert_eq!(ctx.ext.nonce(), 2);
+			exec_success()
+		});
+
+		ExtBuilder::default().build().execute_with(|| {
+			let min_balance = <Test as Config>::Currency::minimum_balance();
+			let schedule = <Test as Config>::Schedule::get();
+			let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+			set_balance(&ALICE, min_balance * 1000);
+			place_contract(&BOB, code_hash);
+			let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap();
+			assert_ok!(MockStack::run_call(
+				ALICE,
+				BOB,
+				&mut gas_meter,
+				&mut storage_meter,
+				&schedule,
+				0,
+				vec![],
+				None,
+				Determinism::Deterministic
+			));
+		});
+	}
 }
diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs
index 9d02989642737..4f2d3b61d0176 100644
--- a/frame/contracts/src/schedule.rs
+++ b/frame/contracts/src/schedule.rs
@@ -430,12 +430,15 @@ pub struct HostFnWeights<T: Config> {
 	/// Weight of calling `seal_ecdsa_to_eth_address`.
 	pub ecdsa_to_eth_address: u64,
 
-	/// Weight of calling `seal_reentrance_count`.
+	/// Weight of calling `reentrance_count`.
 	pub reentrance_count: u64,
 
-	/// Weight of calling `seal_account_reentrance_count`.
+	/// Weight of calling `account_reentrance_count`.
 	pub account_reentrance_count: u64,
 
+	/// Weight of calling `instantiation_nonce`.
+	pub instantiation_nonce: u64,
+
 	/// The type parameter is used in the default implementation.
 	#[codec(skip)]
 	pub _phantom: PhantomData<T>,
@@ -676,6 +679,7 @@ impl<T: Config> Default for HostFnWeights<T> {
 			ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address),
 			reentrance_count: cost_batched!(seal_reentrance_count),
 			account_reentrance_count: cost_batched!(seal_account_reentrance_count),
+			instantiation_nonce: cost_batched!(seal_instantiation_nonce),
 			_phantom: PhantomData,
 		}
 	}
diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs
index ac338007e5dc9..e9e6b42dc3f8a 100644
--- a/frame/contracts/src/wasm/mod.rs
+++ b/frame/contracts/src/wasm/mod.rs
@@ -625,6 +625,9 @@ mod tests {
 		fn account_reentrance_count(&self, _account_id: &AccountIdOf<Self::T>) -> u32 {
 			12
 		}
+		fn nonce(&mut self) -> u64 {
+			995
+		}
 	}
 
 	fn execute_internal<E: BorrowMut<MockExt>>(
@@ -649,16 +652,16 @@ mod tests {
 	}
 
 	fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, ext: E) -> ExecResult {
-		execute_internal(wat, input_data, ext, false)
+		execute_internal(wat, input_data, ext, true)
 	}
 
 	#[cfg(not(feature = "runtime-benchmarks"))]
-	fn execute_with_unstable<E: BorrowMut<MockExt>>(
+	fn execute_no_unstable<E: BorrowMut<MockExt>>(
 		wat: &str,
 		input_data: Vec<u8>,
 		ext: E,
 	) -> ExecResult {
-		execute_internal(wat, input_data, ext, true)
+		execute_internal(wat, input_data, ext, false)
 	}
 
 	const CODE_TRANSFER: &str = r#"
@@ -2971,13 +2974,39 @@ mod tests {
 		execute(CODE, vec![], &mut mock_ext).unwrap();
 	}
 
+	#[test]
+	fn instantiation_nonce_works() {
+		const CODE: &str = r#"
+(module
+	(import "seal0" "instantiation_nonce" (func $nonce (result i64)))
+	(func $assert (param i32)
+		(block $ok
+			(br_if $ok
+				(get_local 0)
+			)
+			(unreachable)
+		)
+	)
+	(func (export "call")
+		(call $assert
+			(i64.eq (call $nonce) (i64.const 995))
+		)
+	)
+	(func (export "deploy"))
+)
+"#;
+
+		let mut mock_ext = MockExt::default();
+		execute(CODE, vec![], &mut mock_ext).unwrap();
+	}
+
 	/// This test check that an unstable interface cannot be deployed. In case of runtime
 	/// benchmarks we always allow unstable interfaces. This is why this test does not
 	/// work when this feature is enabled.
 	#[cfg(not(feature = "runtime-benchmarks"))]
 	#[test]
 	fn cannot_deploy_unstable() {
-		const CANNT_DEPLOY_UNSTABLE: &str = r#"
+		const CANNOT_DEPLOY_UNSTABLE: &str = r#"
 (module
 	(import "seal0" "reentrance_count" (func $reentrance_count (result i32)))
 	(func (export "call"))
@@ -2985,9 +3014,9 @@ mod tests {
 )
 "#;
 		assert_err!(
-			execute(CANNT_DEPLOY_UNSTABLE, vec![], MockExt::default()),
+			execute_no_unstable(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()),
 			<Error<Test>>::CodeRejected,
 		);
-		assert_ok!(execute_with_unstable(CANNT_DEPLOY_UNSTABLE, vec![], MockExt::default()));
+		assert_ok!(execute(CANNOT_DEPLOY_UNSTABLE, vec![], MockExt::default()));
 	}
 }
diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs
index 988bb224f2a6c..b933688eb61ec 100644
--- a/frame/contracts/src/wasm/runtime.rs
+++ b/frame/contracts/src/wasm/runtime.rs
@@ -265,6 +265,8 @@ pub enum RuntimeCosts {
 	ReentrantCount,
 	/// Weight of calling `account_reentrance_count`
 	AccountEntranceCount,
+	/// Weight of calling `instantiation_nonce`
+	InstantationNonce,
 }
 
 impl RuntimeCosts {
@@ -344,6 +346,7 @@ impl RuntimeCosts {
 			EcdsaToEthAddress => s.ecdsa_to_eth_address,
 			ReentrantCount => s.reentrance_count,
 			AccountEntranceCount => s.account_reentrance_count,
+			InstantationNonce => s.instantiation_nonce,
 		};
 		RuntimeToken {
 			#[cfg(test)]
@@ -2614,4 +2617,14 @@ pub mod env {
 			ctx.read_sandbox_memory_as(memory, account_ptr)?;
 		Ok(ctx.ext.account_reentrance_count(&account_id))
 	}
+
+	/// Returns a nonce that is unique per contract instantiation.
+	///
+	/// The nonce is incremented for each succesful contract instantiation. This is a
+	/// sensible default salt for contract instantiations.
+	#[unstable]
+	fn instantiation_nonce(ctx: _, _memory: _) -> Result<u64, TrapReason> {
+		ctx.charge_gas(RuntimeCosts::InstantationNonce)?;
+		Ok(ctx.ext.nonce())
+	}
 }
diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs
index 680f4c94ebd9f..c3f3b50097278 100644
--- a/frame/contracts/src/weights.rs
+++ b/frame/contracts/src/weights.rs
@@ -112,6 +112,7 @@ pub trait WeightInfo {
 	fn seal_set_code_hash(r: u32, ) -> Weight;
 	fn seal_reentrance_count(r: u32, ) -> Weight;
 	fn seal_account_reentrance_count(r: u32, ) -> Weight;
+	fn seal_instantiation_nonce(r: u32, ) -> Weight;
 	fn instr_i64const(r: u32, ) -> Weight;
 	fn instr_i64load(r: u32, ) -> Weight;
 	fn instr_i64store(r: u32, ) -> Weight;
@@ -1054,6 +1055,21 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 			.saturating_add(T::DbWeight::get().reads(6))
 			.saturating_add(T::DbWeight::get().writes(3))
 	}
+	// Storage: System Account (r:1 w:0)
+	// Storage: Contracts ContractInfoOf (r:1 w:1)
+	// Storage: Contracts CodeStorage (r:1 w:0)
+	// Storage: Timestamp Now (r:1 w:0)
+	// Storage: System EventTopics (r:2 w:2)
+	// Storage: Contracts Nonce (r:1 w:1)
+	/// The range of component `r` is `[0, 20]`.
+	fn seal_instantiation_nonce(r: u32, ) -> Weight {
+		// Minimum execution time: 293_987 nanoseconds.
+		Weight::from_ref_time(307_154_849)
+			// Standard Error: 27_486
+			.saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into()))
+			.saturating_add(T::DbWeight::get().reads(7))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
 	/// The range of component `r` is `[0, 50]`.
 	fn instr_i64const(r: u32, ) -> Weight {
 		// Minimum execution time: 688 nanoseconds.
@@ -2307,6 +2323,21 @@ impl WeightInfo for () {
 			.saturating_add(RocksDbWeight::get().reads(6))
 			.saturating_add(RocksDbWeight::get().writes(3))
 	}
+	// Storage: System Account (r:1 w:0)
+	// Storage: Contracts ContractInfoOf (r:1 w:1)
+	// Storage: Contracts CodeStorage (r:1 w:0)
+	// Storage: Timestamp Now (r:1 w:0)
+	// Storage: System EventTopics (r:2 w:2)
+	// Storage: Contracts Nonce (r:1 w:1)
+	/// The range of component `r` is `[0, 20]`.
+	fn seal_instantiation_nonce(r: u32, ) -> Weight {
+		// Minimum execution time: 293_987 nanoseconds.
+		Weight::from_ref_time(307_154_849)
+			// Standard Error: 27_486
+			.saturating_add(Weight::from_ref_time(8_759_333).saturating_mul(r.into()))
+			.saturating_add(RocksDbWeight::get().reads(7))
+			.saturating_add(RocksDbWeight::get().writes(4))
+	}
 	/// The range of component `r` is `[0, 50]`.
 	fn instr_i64const(r: u32, ) -> Weight {
 		// Minimum execution time: 688 nanoseconds.