1use async_trait::async_trait;
2use primitive_types::H160;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 neo_builder::TransactionBuilder,
7 neo_clients::{JsonRpcProvider, RpcClient},
8 neo_contract::{
9 traits::{FungibleTokenTrait, SmartContractTrait, TokenTrait},
10 ContractError,
11 },
12 neo_crypto::Secp256r1PublicKey,
13 neo_protocol::Account,
14 neo_types::{
15 serde_with_utils::{deserialize_script_hash, serialize_script_hash},
16 ContractParameter, ContractParameterType, NNSName, ScriptHash, StackItem,
17 },
18};
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct NeoToken<'a, P: JsonRpcProvider> {
22 #[serde(deserialize_with = "deserialize_script_hash")]
23 #[serde(serialize_with = "serialize_script_hash")]
24 script_hash: ScriptHash,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 total_supply: Option<u64>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 decimals: Option<u8>,
29 symbol: Option<String>,
30 #[serde(skip)]
31 provider: Option<&'a RpcClient<P>>,
32}
33
34impl<'a, P: JsonRpcProvider + 'static> NeoToken<'a, P> {
35 pub const NAME: &'static str = "NeoToken";
36 pub const DECIMALS: u8 = 0;
38 pub const SYMBOL: &'static str = "NEO";
39 pub const TOTAL_SUPPLY: u64 = 100_000_000;
40
41 pub(crate) fn new(provider: Option<&'a RpcClient<P>>) -> Self {
42 Self {
43 script_hash: Self::calc_native_contract_hash(Self::NAME).unwrap(),
44 total_supply: Some(Self::TOTAL_SUPPLY),
45 decimals: Some(Self::DECIMALS),
46 symbol: Some(Self::SYMBOL.to_string()),
47 provider,
48 }
49 }
50
51 async fn unclaimed_gas(
54 &self,
55 account: &Account,
56 block_height: i32,
57 ) -> Result<i64, ContractError> {
58 self.unclaimed_gas_contract(&account.get_script_hash(), block_height).await
59 }
60
61 async fn unclaimed_gas_contract(
62 &self,
63 script_hash: &H160,
64 block_height: i32,
65 ) -> Result<i64, ContractError> {
66 Ok(self
67 .call_function_returning_int(
68 "unclaimedGas",
69 vec![script_hash.into(), block_height.into()],
70 )
71 .await
72 .unwrap() as i64)
73 }
74
75 async fn register_candidate(
78 &self,
79 candidate_key: &Secp256r1PublicKey,
80 ) -> Result<TransactionBuilder<P>, ContractError> {
81 self.invoke_function("registerCandidate", vec![candidate_key.into()]).await
82 }
83
84 async fn unregister_candidate(
85 &self,
86 candidate_key: &Secp256r1PublicKey,
87 ) -> Result<TransactionBuilder<P>, ContractError> {
88 self.invoke_function("unregisterCandidate", vec![candidate_key.into()]).await
89 }
90
91 async fn get_committee(&self) -> Result<Vec<Secp256r1PublicKey>, ContractError> {
94 self.call_function_returning_list_of_public_keys("getCommittee")
95 .await
96 .map_err(|e| ContractError::UnexpectedReturnType(e.to_string()))
97 }
98
99 async fn get_candidates(&self) -> Result<Vec<Candidate>, ContractError> {
100 let candidates = self.call_invoke_function("getCandidates", vec![], vec![]).await.unwrap();
101 let item = candidates.stack.first().unwrap();
102 if let StackItem::Array { value: array } = item {
103 Ok(array
104 .to_vec()
105 .chunks(2)
106 .filter_map(|v| {
107 if v.len() == 2 {
108 Some(Candidate::from(v.to_vec()).unwrap())
109 } else {
110 None
111 }
112 })
113 .collect::<Vec<Candidate>>())
114 } else {
115 Err(ContractError::UnexpectedReturnType("Candidates".to_string()))
116 }
117 }
118
119 async fn is_candidate(&self, public_key: &Secp256r1PublicKey) -> Result<bool, ContractError> {
120 Ok(self
121 .get_candidates()
122 .await
123 .unwrap()
124 .into_iter()
125 .any(|c| c.public_key == *public_key))
126 }
127
128 async fn vote(
131 &self,
132 voter: &H160,
133 candidate: Option<&Secp256r1PublicKey>,
134 ) -> Result<TransactionBuilder<P>, ContractError> {
135 let params = match candidate {
136 Some(key) => vec![voter.into(), key.into()],
137 None => vec![voter.into(), ContractParameter::new(ContractParameterType::Any)],
138 };
139
140 self.invoke_function("vote", params).await
141 }
142
143 async fn cancel_vote(&self, voter: &H160) -> Result<TransactionBuilder<P>, ContractError> {
144 self.vote(voter, None).await
145 }
146
147 async fn build_vote_script(
148 &self,
149 voter: &H160,
150 candidate: Option<&Secp256r1PublicKey>,
151 ) -> Result<Vec<u8>, ContractError> {
152 let params = match candidate {
153 Some(key) => vec![voter.into(), key.into()],
154 None => vec![voter.into(), ContractParameter::new(ContractParameterType::Any)],
155 };
156
157 self.build_invoke_function_script("vote", params).await
158 }
159
160 async fn get_gas_per_block(&self) -> Result<i32, ContractError> {
163 self.call_function_returning_int("getGasPerBlock", vec![]).await
164 }
165
166 async fn set_gas_per_block(
167 &self,
168 gas_per_block: i32,
169 ) -> Result<TransactionBuilder<P>, ContractError> {
170 self.invoke_function("setGasPerBlock", vec![gas_per_block.into()]).await
171 }
172
173 async fn get_register_price(&self) -> Result<i32, ContractError> {
174 self.call_function_returning_int("getRegisterPrice", vec![]).await
175 }
176
177 async fn set_register_price(
178 &self,
179 register_price: i32,
180 ) -> Result<TransactionBuilder<P>, ContractError> {
181 self.invoke_function("setRegisterPrice", vec![register_price.into()]).await
182 }
183
184 async fn get_account_state(&self, account: &H160) -> Result<AccountState, ContractError> {
185 let result = self
186 .call_invoke_function("getAccountState", vec![account.into()], vec![])
187 .await
188 .unwrap()
189 .stack
190 .first()
191 .unwrap()
192 .clone();
193
194 match result {
195 StackItem::Any => Ok(AccountState::with_no_balance()),
196 StackItem::Array { value: items } if items.len() >= 3 => {
197 let balance = items[0].as_int().unwrap();
198 let update_height = items[1].as_int();
199 let public_key = items[2].clone();
200
201 if let StackItem::Any = public_key {
202 return Ok(AccountState {
203 balance,
204 balance_height: update_height,
205 public_key: None,
206 });
207 } else {
208 let pubkey =
209 Secp256r1PublicKey::from_bytes(public_key.as_bytes().unwrap().as_slice())
210 .unwrap();
211 Ok(AccountState {
212 balance,
213 balance_height: update_height,
214 public_key: Some(pubkey),
215 })
216 }
217 },
218 _ => Err(ContractError::InvalidNeoName("Account state malformed".to_string())),
219 }
220 }
221
222 async fn call_function_returning_list_of_public_keys(
223 &self,
224 function: &str,
225 ) -> Result<Vec<Secp256r1PublicKey>, ContractError> {
226 let result = self.call_invoke_function(function, vec![], vec![]).await.unwrap();
227 let stack_item = result.stack.first().unwrap();
228
229 if let StackItem::Array { value: array } = stack_item {
230 let keys = array
231 .iter()
232 .map(|item| {
233 if let StackItem::ByteString { value: bytes } = item {
234 Secp256r1PublicKey::from_bytes(bytes.as_bytes()).map_err(|_| {
235 ContractError::InvalidNeoName("Invalid public key bytes".to_string())
236 })
237 } else {
238 Err(ContractError::UnexpectedReturnType(format!(
239 "Expected ByteString, got {:?}",
240 item
241 )))
242 }
243 })
244 .collect::<Result<Vec<Secp256r1PublicKey>, ContractError>>()?;
245
246 Ok(keys)
247 } else {
248 Err(ContractError::UnexpectedReturnType("Expected Array".to_string()))
249 }
250 }
251
252 async fn resolve_nns_text_record(&self, _name: &NNSName) -> Result<H160, ContractError> {
253 Err(ContractError::UnsupportedOperation(
256 "NNS text record resolution is not supported for NEO token".to_string(),
257 ))
258 }
259}
260
261#[async_trait]
262impl<'a, P: JsonRpcProvider> TokenTrait<'a, P> for NeoToken<'a, P> {
263 fn total_supply(&self) -> Option<u64> {
264 self.total_supply
265 }
266
267 fn set_total_supply(&mut self, total_supply: u64) {
268 self.total_supply = Some(total_supply)
269 }
270
271 fn decimals(&self) -> Option<u8> {
272 self.decimals
273 }
274
275 fn set_decimals(&mut self, decimals: u8) {
276 self.decimals = Some(decimals)
277 }
278
279 fn symbol(&self) -> Option<String> {
280 self.symbol.clone()
281 }
282
283 fn set_symbol(&mut self, symbol: String) {
284 self.symbol = Some(symbol)
285 }
286
287 async fn resolve_nns_text_record(&self, _name: &NNSName) -> Result<H160, ContractError> {
288 Err(ContractError::UnsupportedOperation(
291 "NNS text record resolution is not supported for NEO token".to_string(),
292 ))
293 }
294}
295
296#[async_trait]
297impl<'a, P: JsonRpcProvider> SmartContractTrait<'a> for NeoToken<'a, P> {
298 type P = P;
299
300 fn script_hash(&self) -> H160 {
301 self.script_hash
302 }
303
304 fn set_script_hash(&mut self, script_hash: H160) {
305 self.script_hash = script_hash;
306 }
307
308 fn provider(&self) -> Option<&RpcClient<P>> {
309 self.provider
310 }
311}
312
313#[async_trait]
314impl<'a, P: JsonRpcProvider> FungibleTokenTrait<'a, P> for NeoToken<'a, P> {}
315
316pub struct Candidate {
317 pub public_key: Secp256r1PublicKey,
318 pub votes: i32,
319}
320
321impl Candidate {
322 fn from(items: Vec<StackItem>) -> Result<Self, ContractError> {
323 let key = items[0].as_public_key().unwrap();
324 let votes = items[1].as_int().unwrap() as i32;
325 Ok(Self { public_key: key, votes })
326 }
327}
328
329pub struct AccountState {
330 pub balance: i64,
331 pub balance_height: Option<i64>,
332 pub public_key: Option<Secp256r1PublicKey>,
333}
334
335impl AccountState {
336 pub fn with_no_balance() -> Self {
337 Self { balance: 0, balance_height: None, public_key: None }
338 }
339}