neo3/neo_contract/
neo_token.rs

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 SCRIPT_HASH: H160 = Self::calc_native_contract_hash(Self::NAME).unwrap();
37	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	// Unclaimed Gas
52
53	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	// Candidate Registration
76
77	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	// Committee and Candidates Information
92
93	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	// Voting
129
130	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	// Network Settings
161
162	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		// NEO token doesn't support NNS text record resolution
254		// Return an error indicating this operation is not supported
255		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		// NEO token doesn't support NNS text record resolution
289		// Return an error indicating this operation is not supported
290		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}