neo3/neo_contract/traits/
smart_contract.rs

1use std::{collections::HashMap, sync::Arc};
2
3use crate::neo_crypto::utils::ToHexString;
4use async_trait::async_trait;
5use num_bigint::BigInt;
6use primitive_types::H160;
7
8// Replace prelude imports with specific types
9use crate::{
10	neo_builder::{BuilderError, CallFlags, ScriptBuilder},
11	neo_clients::{APITrait, JsonRpcProvider, ProviderError, RpcClient},
12	neo_contract::{ContractError, NeoIterator},
13	neo_types::{
14		Bytes, ContractManifest, ContractParameter, ContractParameterType, InvocationResult,
15		OpCode, ScriptHash, StackItem,
16	},
17	ScriptHashExtension,
18};
19
20// Import transaction types from the correct modules
21use crate::neo_builder::{Signer, Transaction, TransactionBuilder};
22
23#[async_trait]
24pub trait SmartContractTrait<'a>: Send + Sync {
25	const DEFAULT_ITERATOR_COUNT: usize = 100;
26	type P: JsonRpcProvider;
27
28	async fn name(&self) -> String {
29		self.get_manifest().await.name.clone().unwrap()
30	}
31	fn set_name(&mut self, _name: String) {
32		// NNS contracts don't support setting names
33		// This is intentionally a no-op as it's not supported
34		eprintln!("Warning: Cannot set name for NNS contract - operation not supported");
35	}
36
37	fn script_hash(&self) -> H160;
38
39	fn set_script_hash(&mut self, _script_hash: H160) {
40		// NNS contracts don't support setting script hash
41		// This is intentionally a no-op as it's not supported
42		eprintln!("Warning: Cannot set script hash for NNS contract - operation not supported");
43	}
44
45	fn provider(&self) -> Option<&RpcClient<Self::P>>;
46
47	async fn invoke_function(
48		&self,
49		function: &str,
50		params: Vec<ContractParameter>,
51	) -> Result<TransactionBuilder<Self::P>, ContractError> {
52		let script = self.build_invoke_function_script(function, params).await.unwrap();
53		let mut builder = TransactionBuilder::new();
54		builder.set_script(Some(script));
55		Ok(builder)
56	}
57
58	async fn build_invoke_function_script(
59		&self,
60		function: &str,
61		params: Vec<ContractParameter>,
62	) -> Result<Bytes, ContractError> {
63		if function.is_empty() {
64			return Err(ContractError::InvalidNeoName("Function name cannot be empty".to_string()));
65		}
66
67		let script = ScriptBuilder::new()
68			.contract_call(&self.script_hash(), function, params.as_slice(), Some(CallFlags::None))
69			.unwrap()
70			.to_bytes();
71
72		Ok(script)
73	}
74
75	async fn call_function_returning_string(
76		&self,
77		function: &str,
78		params: Vec<ContractParameter>,
79	) -> Result<String, ContractError> {
80		let output = self.call_invoke_function(function, params, vec![]).await.unwrap();
81		self.throw_if_fault_state(&output).unwrap();
82
83		let item = output.stack[0].clone();
84		match item.as_string() {
85			Some(s) => Ok(s),
86			None => Err(ContractError::UnexpectedReturnType("String".to_string())),
87		}
88	}
89
90	async fn call_function_returning_int(
91		&self,
92		function: &str,
93		params: Vec<ContractParameter>,
94	) -> Result<i32, ContractError> {
95		let output = self.call_invoke_function(function, params, vec![]).await.unwrap();
96		self.throw_if_fault_state(&output).unwrap();
97
98		let item = output.stack[0].clone();
99		match item.as_int() {
100			Some(i) => Ok(i as i32),
101			None => Err(ContractError::UnexpectedReturnType("Int".to_string())),
102		}
103	}
104
105	async fn call_function_returning_bool(
106		&self,
107		function: &str,
108		params: Vec<ContractParameter>,
109	) -> Result<bool, ContractError> {
110		let output = self.call_invoke_function(function, params, vec![]).await.unwrap();
111		self.throw_if_fault_state(&output).unwrap();
112
113		let item = output.stack[0].clone();
114		match item.as_bool() {
115			Some(b) => Ok(b),
116			None => Err(ContractError::UnexpectedReturnType("Bool".to_string())),
117		}
118	}
119
120	// Other methods
121
122	async fn call_invoke_function(
123		&self,
124		function: &str,
125		params: Vec<ContractParameter>,
126		signers: Vec<Signer>,
127	) -> Result<InvocationResult, ContractError> {
128		if function.is_empty() {
129			return Err(ContractError::from(ContractError::InvalidNeoName(
130				"Function cannot be empty".to_string(),
131			)));
132		}
133
134		let res = self
135			.provider()
136			.unwrap()
137			.invoke_function(&self.script_hash().clone(), function.into(), params, Some(signers))
138			.await?
139			.clone();
140
141		Ok(res)
142	}
143
144	fn throw_if_fault_state(&self, output: &InvocationResult) -> Result<(), ContractError> {
145		if output.has_state_fault() {
146			Err(ContractError::UnexpectedReturnType(output.exception.clone().unwrap()))
147		} else {
148			Ok(())
149		}
150	}
151
152	// Other methods like `call_function_returning_xxx`, iterators, etc.
153	async fn call_function_returning_script_hash(
154		&self,
155		function: &str,
156		params: Vec<ContractParameter>,
157	) -> Result<H160, ContractError> {
158		let output = self.call_invoke_function(function, params, vec![]).await.unwrap();
159		self.throw_if_fault_state(&output).unwrap();
160
161		let item = &output.stack[0];
162		item.as_bytes()
163			.as_deref()
164			.map(|b| ScriptHash::from_script(b))
165			.ok_or_else(|| ContractError::UnexpectedReturnType("Script hash".to_string()))
166	}
167
168	async fn call_function_returning_iterator<U>(
169		&self,
170		function: &str,
171		params: Vec<ContractParameter>,
172		mapper: Arc<dyn Fn(StackItem) -> U + Send + Sync>,
173	) -> Result<NeoIterator<U, Self::P>, ContractError>
174	where
175		U: Send + Sync, // Adding this bound if necessary
176	{
177		let output = self.call_invoke_function(function, params, vec![]).await.unwrap();
178		self.throw_if_fault_state(&output).unwrap();
179
180		let item = &output.stack[0];
181		let StackItem::InteropInterface { id, interface: _ } = item else {
182			return Err(ContractError::UnexpectedReturnType(format!(
183				"Expected InteropInterface, got {:?}",
184				item
185			)));
186		};
187
188		let session_id = output
189			.session_id
190			.ok_or(ContractError::InvalidNeoNameServiceRoot("No session ID".to_string()))?;
191
192		Ok(NeoIterator::new(session_id, id.clone(), mapper, None))
193	}
194
195	async fn call_function_and_unwrap_iterator<U>(
196		&self,
197		function: &str,
198		params: Vec<ContractParameter>,
199		_max_items: usize,
200		mapper: impl Fn(StackItem) -> U + Send,
201	) -> Result<Vec<U>, ContractError> {
202		let script = ScriptBuilder::build_contract_call_and_unwrap_iterator(
203			&self.script_hash(),
204			function,
205			&params,
206			_max_items as u32, // Use the max_items parameter provided to the function
207			Some(CallFlags::All),
208		)
209		.unwrap();
210
211		let output = { self.provider().unwrap().invoke_script(script.to_hex_string(), vec![]) };
212
213		let output = output.await.unwrap();
214
215		self.throw_if_fault_state(&output).unwrap();
216
217		let items = output.stack[0].as_array().unwrap().into_iter().map(mapper).collect();
218
219		Ok(items)
220	}
221
222	fn calc_native_contract_hash(contract_name: &str) -> Result<H160, ContractError> {
223		Self::calc_contract_hash(H160::zero(), 0, contract_name)
224	}
225
226	fn calc_contract_hash(
227		sender: H160,
228		nef_checksum: u32,
229		contract_name: &str,
230	) -> Result<H160, ContractError> {
231		let mut script = ScriptBuilder::new();
232		script
233			.op_code(&[OpCode::Abort])
234			.push_data(sender.to_vec())
235			.push_integer(BigInt::from(nef_checksum))
236			.push_data(contract_name.as_bytes().to_vec());
237
238		Ok(H160::from_slice(&script.to_bytes()))
239	}
240
241	async fn get_manifest(&self) -> ContractManifest {
242		let req =
243			{ self.provider().unwrap().get_contract_state(self.script_hash()).await.unwrap() };
244
245		req.manifest.clone()
246	}
247}