cpchain_rust_sdk/contract/
mod.rs

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    /// Deploy smart contract
41    /// e.g.
42    /// ```rust
43    /// let bytecode = include_str!("../../fixtures/contracts/Metacoin.bin").trim_end();
44    /// let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
45    /// let account = load_account();
46    /// assert_eq!(
47    ///     account.address.to_checksum(),
48    ///     "0x6CBea203F4061855247cea3843E2e5957C4CD428"
49    /// );
50    /// let balance = web3.balance(&account.address).await.unwrap();
51    /// println!("balance: {:?}", balance);
52    /// let c = Contract::deploy(
53    ///     &web3,
54    ///     include_bytes!("../../fixtures/contracts/Metacoin.abi"),
55    /// )
56    /// .options(Options::with(|opt| {
57    ///     opt.value = Some(0.into());
58    ///     opt.gas_price = Some((180_000_000_000 as u64).into());
59    ///     opt.gas = Some(300_000.into());
60    /// }))
61    /// .sign_with_key_and_execute(bytecode, (), &account, 337.into())
62    /// .await
63    /// .unwrap();
64    /// println!("Address: {:?}", c.address());
65    /// ```
66    pub fn deploy<'a>(web3: &'a CPCWeb3, abi_json: &'a [u8]) -> Deployer<'a> {
67        Deployer::new(web3, abi_json)
68    }
69    /// Get address of this contract
70    pub fn address(&self) -> Address {
71        Address::new(self.contract.address())
72    }
73    /// Call query methods of the smart contract
74    /// e.g.
75    /// ```rust
76    /// let web3 = CPCWeb3::new("https://civilian.cpchain.io").unwrap();
77    /// let account = load_account();
78    /// let c = Contract::from_address(
79    ///     &web3,
80    ///     &Address::from_str("0x8b3b22339466a3c8fd9b78c309aebfbf0bb95a9a").unwrap(),
81    ///     include_bytes!("../../fixtures/contracts/Metacoin.abi"),
82    /// );
83    /// let r = c.query(
84    ///         "getBalance",
85    ///     (account.address.h160,),
86    ///     None,
87    ///     Options::default(),
88    ///     None,
89    /// );
90    /// let balance: U256 = r.await.unwrap();
91    /// println!("balance: {:?}", balance);
92    /// ```
93    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    /// Call functions of this contract
111    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 signed = self.sign(func, params, options, key).await?;
121        let fn_data = self
122            .contract
123            .abi()
124            .function(func)
125            .and_then(|function| function.encode_input(&params.into_tokens()))
126            // TODO [ToDr] SendTransactionWithConfirmation should support custom error type (so that we can return
127            // `contract::Error` instead of more generic `Error`.
128            .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        // 如果事件字段是 indexed 的,则会在 topics 中,其余则在 data 字段中按字节排列
177        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(), // 0xdf7db491b07e8052a43095dd93385d07e7c43e6c
267            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        // 一维数组测试
329        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}