neo3/neo_contract/traits/
smart_contract.rs1use 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
8use 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
20use 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 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 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 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 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, {
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 ¶ms,
206 _max_items as u32, 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}