1use std::future::Future;
2
3use web3::{
4 contract::tokens::{Detokenize, Tokenize},
5 ethabi, types::{FilterBuilder, Log},
6};
7
8use crate::{
9 accounts::Account,
10 address::Address,
11 transport::CPCHttp,
12 types::{Bytes, Options, TransactionParameters, H256, TransactionLog},
13 CPCWeb3, error::StdError,
14};
15
16use self::{deployer::Deployer, event::Event};
17
18mod deployer;
19pub mod event;
20
21pub struct Contract {
22 pub(crate) contract: web3::contract::Contract<CPCHttp>,
23}
24
25impl Contract {
26 pub(crate) fn new(contract: web3::contract::Contract<CPCHttp>) -> Self {
27 Self { contract }
28 }
29 pub fn from_address(web3: &CPCWeb3, address: &Address, abi_json: &[u8]) -> Self {
30 let abi = ethabi::Contract::load(abi_json).unwrap();
31 let eth = web3.web3.eth();
32 Contract::new(web3::contract::Contract::new(eth, address.h160, abi))
33 }
34 pub fn event_sig(&self, event: &str) -> Option<H256> {
35 match self.contract.abi().event(event) {
36 Ok(e) => Some(e.signature()),
37 Err(_) => None
38 }
39 }
40 pub fn deploy<'a>(web3: &'a CPCWeb3, abi_json: &'a [u8]) -> Deployer<'a> {
67 Deployer::new(web3, abi_json)
68 }
69 pub fn address(&self) -> Address {
71 Address::new(self.contract.address())
72 }
73 pub fn query<R, A, B, P>(
94 &self,
95 func: &str,
96 params: P,
97 from: A,
98 options: Options,
99 block: B,
100 ) -> impl Future<Output = web3::contract::Result<R>> + '_
101 where
102 R: Detokenize,
103 A: Into<Option<web3::types::Address>>,
104 B: Into<Option<web3::types::BlockId>>,
105 P: Tokenize,
106 {
107 self.contract.query(func, params, from, options, block)
108 }
109
110 pub async fn signed_call(
112 &self,
113 web3: &CPCWeb3,
114 chain_id: u32,
115 func: &str,
116 params: impl Tokenize,
117 options: Options,
118 account: &Account,
119 ) -> web3::contract::Result<H256> {
120 let fn_data = self
122 .contract
123 .abi()
124 .function(func)
125 .and_then(|function| function.encode_input(¶ms.into_tokens()))
126 .map_err(|err| web3::error::Error::Decoder(format!("{:?}", err)))?;
129 let nonce = match options.nonce {
130 Some(nonce) => nonce,
131 None => web3.transaction_count(&account.address).await?,
132 };
133 let tx = TransactionParameters::new(
134 chain_id.into(),
135 nonce,
136 Some(self.address().h160),
137 options.gas.unwrap_or(300_000.into()),
138 options.gas_price.unwrap_or(web3.gas_price().await?),
139 options.value.unwrap_or(0.into()),
140 Bytes::from(fn_data),
141 );
142 let signed_tx = web3.sign_transaction(&account, &tx).await?;
143 let hash = web3.submit_signed_raw_tx(&signed_tx).await?;
144 Ok(hash)
145 }
146
147 pub async fn logs(&self, web3: &CPCWeb3, event_name: &str, from_block: Option<u64>, to_block: Option<u64>) -> Result<Vec<Log>, StdError> {
148 let sig = match self.event_sig(event_name) {
149 Some(sig) => sig,
150 None => return Err(format!("Not found event `{}`", event_name).into())
151 };
152 let mut builder = FilterBuilder::default()
153 .address(vec![self.address().h160])
154 .topics(
155 Some(vec![sig]),
156 None,
157 None,
158 None,
159 );
160 if from_block.is_some() {
161 builder = builder.from_block(from_block.unwrap().into());
162 }
163 if to_block.is_some() {
164 builder = builder.to_block(to_block.unwrap().into());
165 }
166 let filter = builder.build();
167 match web3.web3.eth().logs(filter).await {
168 Ok(logs) => Ok(logs),
169 Err(e) => Err(format!("Get logs failed: {}", e).into())
170 }
171 }
172
173 pub async fn events(&self, web3: &CPCWeb3, event_name: &str, from_block: Option<u64>, to_block: Option<u64>) -> Result<Vec<Event>, StdError> {
174 let logs = self.logs(web3, event_name, from_block, to_block).await?;
175 let e = self.contract.abi().event(event_name).unwrap();
176 Ok(Event::from_logs(e, &logs)?)
178 }
179
180 pub fn parse_log(abi_json: &[u8], event_name: &str, log: &TransactionLog) -> Result<Event, StdError> {
181 let abi = ethabi::Contract::load(abi_json)?;
182 let e = abi.event(event_name)?;
183 Event::from(e, &log)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use std::str::FromStr;
190
191 use super::*;
192 use crate::{
193 accounts::Account,
194 types::{Options, H160, U256},
195 CPCWeb3,
196 };
197
198 fn load_account() -> Account {
199 Account::from_keystore(
200 include_str!("../../keystore/0x6CBea203F4061855247cea3843E2e5957C4CD428.json"),
201 "123456",
202 )
203 .unwrap()
204 }
205
206 #[tokio::test]
207 async fn test_deploy_contract() {
208 let bytecode = include_str!("../../fixtures/contracts/Metacoin.bin").trim_end();
209 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
210 let account = load_account();
211 assert_eq!(
212 account.address.to_checksum(),
213 "0x6CBea203F4061855247cea3843E2e5957C4CD428"
214 );
215 let balance = web3.balance(&account.address).await.unwrap();
216 println!("balance: {:?}", balance);
217 let c = Contract::deploy(
218 &web3,
219 include_bytes!("../../fixtures/contracts/Metacoin.abi"),
220 )
221 .options(Options::with(|opt| {
222 opt.value = Some(0.into());
223 opt.gas_price = Some((180_000_000_000 as u64).into());
224 opt.gas = Some(3000_000.into());
225 }))
226 .sign_with_key_and_execute(bytecode, (), &account, 337.into())
227 .await
228 .unwrap();
229 println!("Address: {:?}", c.address());
230 let r = c.query(
231 "getBalance",
232 (account.address.h160,),
233 None,
234 Options::default(),
235 None,
236 );
237 let balance: U256 = r.await.unwrap();
238 println!("balance: {:?}", balance);
239 }
240
241 #[tokio::test]
242 async fn test_query() {
243 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
244 let account = load_account();
245 let c = Contract::from_address(
246 &web3,
247 &Address::from_str("0x18b1385f172fed71d25d4faf306fb064131e8b4c").unwrap(),
248 include_bytes!("../../fixtures/contracts/Metacoin.abi"),
249 );
250 let r = c.query(
251 "getBalance",
252 (account.address.h160,),
253 None,
254 Options::default(),
255 None,
256 );
257 let balance: U256 = r.await.unwrap();
258 println!("balance: {:?}", balance);
259 }
260 #[tokio::test]
261 async fn test_call() {
262 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
263 let account = load_account();
264 let c = Contract::from_address(
265 &web3,
266 &Address::from_str("0xcbefb53f853bd7ea355c1e48c250cafe60d24e67").unwrap(), include_bytes!("../../fixtures/contracts/Metacoin.abi"),
268 );
269 let hash = c
270 .signed_call(
271 &web3,
272 337,
273 "sendCoin",
274 (
275 H160::from_str("0xFD10B944FFC7Be13516C003eeE6cEf7335d031e9").unwrap(),
276 U256::from(8),
277 ),
278 Options::default(),
279 &account,
280 )
281 .await
282 .unwrap();
283 let receipt = web3.wait_tx(&hash).await.unwrap();
284 println!("{:?}", receipt);
285 }
286
287 #[tokio::test]
288 async fn test_events() {
289 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
290 let c = Contract::from_address(
291 &web3,
292 &Address::from_str("0xcbefb53f853bd7ea355c1e48c250cafe60d24e67").unwrap(),
293 include_bytes!("../../fixtures/contracts/Metacoin.abi"),
294 );
295 let events = c.events(&web3, "Transfer", Some(0), None).await.unwrap();
296 println!("{:?}", events);
297 println!("Length {:?}", events.len())
298 }
299
300 #[tokio::test]
301 async fn test_term_events() {
302 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
303 let c = Contract::from_address(
304 &web3,
305 &Address::from_str("0xd6382b0757C691cb502D0b47264F70d50F236226").unwrap(),
306 include_bytes!("../../fixtures/contracts/Term.abi.json"),
307 );
308 let events = c.events(&web3, "TermCreated", Some(0), None).await.unwrap();
309 println!("{:?}", events);
310 println!("Length {:?}", events.len())
311 }
312
313 #[tokio::test]
314 async fn test_roadmap_events() {
315 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
316 let c = Contract::from_address(
317 &web3,
318 &Address::from_str("0xc3aA832c3ba3c005b93E35850874e3fD212a832C").unwrap(),
319 include_bytes!("../../fixtures/contracts/Roadmap.abi.json"),
320 );
321 let events = c.events(&web3, "RegisterRoadmap", Some(0), None).await.unwrap();
322 println!("{:?}", events);
323 println!("Length {:?}", events.len())
324 }
325
326 #[tokio::test]
327 async fn test_one_dime_events() {
328 let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
330 let abi = "[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"xx\",\"type\":\"string[]\"}],\"name\":\"E1\",\"type\":\"event\"}]".as_bytes();
331 let c = Contract::from_address(
332 &web3,
333 &Address::from_str("0x1DaD5F2372B463eD0Db6A8c0ba3B0b6E7196fb18").unwrap(),
334 abi
335 );
336 let events = c.events(&web3, "E1", Some(0), None).await.unwrap();
337 println!("{:?}", events);
338 println!("Length {:?}", events.len())
339 }
340}