cpchain_rust_sdk/
cpc_web3.rs1use std::time::{Duration, Instant};
2
3use web3::{
4 types::{Block, BlockId, SignedTransaction, Transaction, TransactionReceipt, H160, H256, U256, BlockNumber},
5 Error, Web3,
6};
7
8use crate::{
9 accounts::Account, address::Address, transport::CPCHttp, types::TransactionParameters,
10};
11
12#[derive(Debug, Clone)]
13pub struct CPCWeb3 {
14 pub web3: Web3<CPCHttp>,
15}
16
17impl CPCWeb3 {
18 pub fn new(url: &str) -> Result<Self, Error> {
19 let transport = CPCHttp::new(url)?;
20 let web3 = web3::Web3::new(transport);
21 Ok(Self { web3 })
22 }
23 pub async fn block_number(&self) -> Result<u64, Error> {
24 let current_block = self.web3.eth().block_number().await?;
25 Ok(current_block.as_u64())
26 }
27
28 pub async fn block(&self, number: Option<u32>) -> Result<Option<Block<H256>>, Error> {
29 let number = match number {
30 Some(n) => n.into(),
31 None => BlockNumber::Latest,
32 };
33 self.web3.eth().block(BlockId::Number(number)).await
34 }
35
36 pub async fn block_with_txs(&self, number: Option<u32>) -> Result<Option<Block<Transaction>>, Error> {
37 let number = match number {
38 Some(n) => n.into(),
39 None => BlockNumber::Latest,
40 };
41 self.web3
42 .eth()
43 .block_with_txs(BlockId::Number(number))
44 .await
45 }
46
47 pub async fn balance(&self, address: &Address) -> Result<U256, Error> {
48 let balance = self.web3.eth().balance(address.h160, None).await?;
49 Ok(balance)
50 }
51
52 pub async fn sign_transaction(
53 &self,
54 account: &Account,
55 tx: &TransactionParameters,
56 ) -> Result<SignedTransaction, Error> {
57 let signed = tx.sign(&account.secret_key);
58 Ok(signed)
59 }
60
61 pub async fn gas_price(&self) -> Result<U256, Error> {
62 Ok(self.web3.eth().gas_price().await?)
63 }
64
65 pub async fn transaction_count(&self, address: &Address) -> Result<U256, Error> {
66 self.web3.eth().transaction_count(address.h160, None).await
67 }
68
69 pub async fn submit_signed_raw_tx(&self, signed: &SignedTransaction) -> Result<H256, Error> {
70 self.web3
71 .eth()
72 .send_raw_transaction(signed.raw_transaction.clone())
73 .await
74 }
75
76 pub async fn wait_tx(
77 &self,
78 tx_hash: &H256,
79 ) -> Result<TransactionReceipt, Box<dyn std::error::Error>> {
80 let start = Instant::now();
81 let timeout = Duration::from_secs(20);
82 loop {
83 let receipt = self.web3.eth().transaction_receipt(*tx_hash).await?;
84 if receipt.is_some() {
85 return Ok(receipt.unwrap());
86 }
87 if start.elapsed() >= timeout {
88 return Err("Waiting for transaction receipt timed out".into());
89 }
90 }
91 }
92
93 pub async fn estimate_gas(&self, req: &TransactionParameters) -> Result<U256, Error> {
94 self.web3
95 .eth()
96 .estimate_gas(req.to_call_request(), None)
97 .await
98 }
99
100 pub async fn transaction_receipt(
101 &self,
102 tx_hash: &H256,
103 ) -> Result<Option<TransactionReceipt>, Error> {
104 self.web3.eth().transaction_receipt(*tx_hash).await
105 }
106
107 pub async fn is_contract(&self, addr: H160) -> Result<bool, Error> {
108 let code = self.web3.eth().code(addr, None).await?;
109 Ok(code.0.len() > 0)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::types::Bytes;
116
117 use super::*;
118
119 #[tokio::test]
120 async fn test_block_number() {
121 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
122 let number = web3.block_number().await.unwrap();
123 println!("{:?}", number);
124 assert!(number > 0);
125 }
126
127 #[tokio::test]
128 async fn test_get_block_with_txs() {
129 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
130 let block = web3.block_with_txs(Some(100)).await.unwrap();
131 assert!(block.is_some());
132 let block = block.unwrap();
133 assert!(block.number.unwrap().as_u32() == 100);
134 assert!(block.transactions.len() == 0);
135 assert!(block.size.unwrap().as_u32() == 1263);
136 assert!(block.hash.unwrap().to_string().to_lowercase() == "0x1b91…4aef");
137 let block = web3.block_with_txs(Some(10504047)).await.unwrap();
138 assert!(block.is_some());
139 let block = block.unwrap();
140 assert!(block.number.unwrap().as_u32() == 10504047);
141 assert!(block.transactions.len() == 24);
142 assert!(block.size.unwrap().as_u32() == 8619);
143 assert!(block.hash.unwrap().to_string().to_lowercase() == "0x37e1…0e7b");
144 let tx = &block.transactions[0];
146 assert!(tx.hash.to_string() == "0x89cd…6ada");
147 assert!(tx.from.unwrap().to_string() == "0x5e17…b6f0");
148 assert!(tx.to.unwrap().to_string() == "0x2a18…fcd0");
149 assert!(tx.value.to_string() == "0");
150 assert!(tx.gas.to_string() == "2300000");
151 assert!(tx.gas_price.unwrap().to_string() == "18000000000");
152 let tx = &block.transactions.last().unwrap();
153 assert!(tx.hash.to_string() == "0xc5b0…5e62");
154
155 let block = web3.block_with_txs(Some(100000000)).await.unwrap();
157 assert!(block.is_none());
158 }
159
160 #[tokio::test]
161 async fn test_get_block() {
162 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
163 let block = web3.block(Some(100)).await.unwrap().unwrap();
164 assert!(block.number.unwrap().as_u32() == 100);
165 }
166
167 #[tokio::test]
168 async fn test_get_latest_block() {
169 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
170 let block = web3.block(None).await.unwrap().unwrap();
171 assert!(block.number.unwrap().as_u32() > 100);
172 }
173
174 #[tokio::test]
175 async fn test_get_balance() {
176 let web3 = CPCWeb3::new("http://192.168.0.164:8501").unwrap();
177 let balance = web3
178 .balance(&Address::from_str("0x7D491C482eBa270700b584888f864177205c5159").unwrap())
179 .await
180 .unwrap();
181 println!("{:?}", balance.as_u128());
182 }
183
184 #[tokio::test]
185 async fn test_gas_price() {
186 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
187 let gas_price = web3.gas_price().await.unwrap();
188 assert_eq!(gas_price, U256::from(18) * U256::exp10(9))
189 }
190
191 #[tokio::test]
192 async fn test_get_transactions_cnt() {
193 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
194 let cnt = web3
195 .transaction_count(
196 &Address::from_str("0x1455D180E3adE94ebD9cC324D22a9065d1F5F575").unwrap(),
197 )
198 .await
199 .unwrap();
200 assert!(cnt >= U256::from(1065));
201 let cnt = web3
202 .transaction_count(
203 &Address::from_str("0x2455D180E3adE94ebD9cC324D22a9065d1F5F575").unwrap(),
204 )
205 .await
206 .unwrap();
207 assert!(cnt == U256::from(0))
208 }
209
210 #[tokio::test]
211 async fn test_sign_transaction() {
212 let web3 = CPCWeb3::new("http://192.168.0.164:8501").unwrap();
213 let account = Account::from_phrase(
214 "length much pull abstract almost spin hair chest ankle harbor dizzy life",
215 None,
216 )
217 .unwrap();
218 println!("{}", account.address);
219 let gas_price = web3.gas_price().await.unwrap();
220 let nonce = web3.transaction_count(&account.address).await.unwrap();
221 let tx_object = TransactionParameters::new(
222 41,
223 nonce,
224 Some(Address::from_str("0x1455D180E3adE94ebD9cC324D22a9065d1F5F575").unwrap().h160),
225 300000.into(),
226 gas_price,
227 U256::exp10(17), Bytes::default(),
229 );
230
231 let estimated_gas = web3.estimate_gas(&tx_object).await.unwrap();
232 assert_eq!(estimated_gas, U256::from(21000));
233
234 let signed = web3.sign_transaction(&account, &tx_object).await.unwrap();
235 let tx_hash = web3.submit_signed_raw_tx(&signed).await.unwrap();
236 println!("{:?} {:?}", tx_hash, signed.transaction_hash);
237 assert_eq!(tx_hash, signed.transaction_hash);
238 let receipt = web3.wait_tx(&tx_hash).await.unwrap();
240 println!("{:?}", receipt);
241 }
242
243 #[tokio::test]
244 async fn test_is_contract() {
245 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
246 assert_eq!(
247 web3.is_contract(
248 Address::from_str("0xcf3174cd4dc7c4834d8932f7c3800ab98afc437a")
249 .unwrap()
250 .h160
251 )
252 .await
253 .unwrap(),
254 true
255 );
256 assert_eq!(
257 web3.is_contract(
258 Address::from_str("0x1455D180E3adE94ebD9cC324D22a9065d1F5F575")
259 .unwrap()
260 .h160
261 )
262 .await
263 .unwrap(),
264 false
265 );
266 }
267}