1use crate::{
4 api::{Eth, Namespace},
5 confirm,
6 contract::tokens::{Detokenize, Tokenize},
7 futures::Future,
8 types::{
9 AccessList, Address, BlockId, Bytes, CallRequest, FilterBuilder, TransactionCondition, TransactionReceipt,
10 TransactionRequest, H256, U256, U64,
11 },
12 Transport,
13 ic::KeyInfo,
14};
15use std::{collections::HashMap, hash::Hash, time};
16
17pub mod deploy;
18mod error;
20pub mod tokens;
21
22pub use crate::contract::error::Error;
23
24pub type Result<T> = std::result::Result<T, Error>;
26
27#[derive(Default, Debug, Clone, PartialEq)]
29pub struct Options {
30 pub gas: Option<U256>,
32 pub gas_price: Option<U256>,
34 pub value: Option<U256>,
36 pub nonce: Option<U256>,
38 pub condition: Option<TransactionCondition>,
40 pub transaction_type: Option<U64>,
42 pub access_list: Option<AccessList>,
44 pub max_fee_per_gas: Option<U256>,
46 pub max_priority_fee_per_gas: Option<U256>,
48}
49
50impl Options {
51 pub fn with<F>(func: F) -> Options
53 where
54 F: FnOnce(&mut Options),
55 {
56 let mut options = Options::default();
57 func(&mut options);
58 options
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct Contract<T: Transport> {
65 address: Address,
66 eth: Eth<T>,
67 abi: ethabi::Contract,
68}
69
70impl<T: Transport> Contract<T> {
71 pub fn deploy(eth: Eth<T>, json: &[u8]) -> ethabi::Result<deploy::Builder<T>> {
73 let abi = ethabi::Contract::load(json)?;
74 Ok(deploy::Builder {
75 eth,
76 abi,
77 options: Options::default(),
78 confirmations: 1,
79 poll_interval: time::Duration::from_secs(7),
80 linker: HashMap::default(),
81 })
82 }
83
84 pub fn deploy_from_truffle<S>(
86 eth: Eth<T>,
87 json: &[u8],
88 linker: HashMap<S, Address>,
89 ) -> ethabi::Result<deploy::Builder<T>>
90 where
91 S: AsRef<str> + Eq + Hash,
92 {
93 let abi = ethabi::Contract::load(json)?;
94 let linker: HashMap<String, Address> = linker.into_iter().map(|(s, a)| (s.as_ref().to_string(), a)).collect();
95 Ok(deploy::Builder {
96 eth,
97 abi,
98 options: Options::default(),
99 confirmations: 1,
100 poll_interval: time::Duration::from_secs(7),
101 linker,
102 })
103 }
104}
105
106impl<T: Transport> Contract<T> {
107 pub fn new(eth: Eth<T>, address: Address, abi: ethabi::Contract) -> Self {
109 Contract { address, eth, abi }
110 }
111
112 pub fn from_json(eth: Eth<T>, address: Address, json: &[u8]) -> ethabi::Result<Self> {
114 let abi = ethabi::Contract::load(json)?;
115 Ok(Self::new(eth, address, abi))
116 }
117
118 pub fn abi(&self) -> ðabi::Contract {
120 &self.abi
121 }
122
123 pub fn address(&self) -> Address {
125 self.address
126 }
127
128 pub async fn call<P>(&self, func: &str, params: P, from: Address, options: Options) -> Result<H256>
130 where
131 P: Tokenize,
132 {
133 let data = self.abi.function(func)?.encode_input(¶ms.into_tokens())?;
134 let Options {
135 gas,
136 gas_price,
137 value,
138 nonce,
139 condition,
140 transaction_type,
141 access_list,
142 max_fee_per_gas,
143 max_priority_fee_per_gas,
144 } = options;
145 self.eth
146 .send_transaction(TransactionRequest {
147 from,
148 to: Some(self.address),
149 gas,
150 gas_price,
151 value,
152 nonce,
153 data: Some(Bytes(data)),
154 condition,
155 transaction_type,
156 access_list,
157 max_fee_per_gas,
158 max_priority_fee_per_gas,
159 })
160 .await
161 .map_err(Error::from)
162 }
163
164 pub async fn call_with_confirmations(
166 &self,
167 func: &str,
168 params: impl Tokenize,
169 from: Address,
170 options: Options,
171 confirmations: usize,
172 ) -> crate::error::Result<TransactionReceipt> {
173 let poll_interval = time::Duration::from_secs(1);
174
175 let fn_data = self
176 .abi
177 .function(func)
178 .and_then(|function| function.encode_input(¶ms.into_tokens()))
179 .map_err(|err| crate::error::Error::Decoder(format!("{:?}", err)))?;
182 let transaction_request = TransactionRequest {
183 from,
184 to: Some(self.address),
185 gas: options.gas,
186 gas_price: options.gas_price,
187 value: options.value,
188 nonce: options.nonce,
189 data: Some(Bytes(fn_data)),
190 condition: options.condition,
191 transaction_type: options.transaction_type,
192 access_list: options.access_list,
193 max_fee_per_gas: options.max_fee_per_gas,
194 max_priority_fee_per_gas: options.max_priority_fee_per_gas,
195 };
196 confirm::send_transaction_with_confirmation(
197 self.eth.transport().clone(),
198 transaction_request,
199 poll_interval,
200 confirmations,
201 )
202 .await
203 }
204
205 pub async fn estimate_gas<P>(&self, func: &str, params: P, from: Address, options: Options) -> Result<U256>
207 where
208 P: Tokenize,
209 {
210 let data = self.abi.function(func)?.encode_input(¶ms.into_tokens())?;
211 self.eth
212 .estimate_gas(
213 CallRequest {
214 from: Some(from),
215 to: Some(self.address),
216 gas: options.gas,
217 gas_price: options.gas_price,
218 value: options.value,
219 data: Some(Bytes(data)),
220 transaction_type: options.transaction_type,
221 access_list: options.access_list,
222 max_fee_per_gas: options.max_fee_per_gas,
223 max_priority_fee_per_gas: options.max_priority_fee_per_gas,
224 },
225 None,
226 )
227 .await
228 .map_err(Into::into)
229 }
230
231 pub fn query<R, A, B, P>(
233 &self,
234 func: &str,
235 params: P,
236 from: A,
237 options: Options,
238 block: B,
239 ) -> impl Future<Output = Result<R>> + '_
240 where
241 R: Detokenize,
242 A: Into<Option<Address>>,
243 B: Into<Option<BlockId>>,
244 P: Tokenize,
245 {
246 let result = self
247 .abi
248 .function(func)
249 .and_then(|function| {
250 function
251 .encode_input(¶ms.into_tokens())
252 .map(|call| (call, function))
253 })
254 .map(|(call, function)| {
255 let call_future = self.eth.call(
256 CallRequest {
257 from: from.into(),
258 to: Some(self.address),
259 gas: options.gas,
260 gas_price: options.gas_price,
261 value: options.value,
262 data: Some(Bytes(call)),
263 transaction_type: options.transaction_type,
264 access_list: options.access_list,
265 max_fee_per_gas: options.max_fee_per_gas,
266 max_priority_fee_per_gas: options.max_priority_fee_per_gas,
267 },
268 block.into(),
269 );
270 (call_future, function)
271 });
272 async {
275 let (call_future, function) = result?;
276 let bytes = call_future.await?;
277 let output = function.decode_output(&bytes.0)?;
278 R::from_tokens(output)
279 }
280 }
281
282 pub async fn events<A, B, C, R>(&self, event: &str, topic0: A, topic1: B, topic2: C) -> Result<Vec<R>>
284 where
285 A: Tokenize,
286 B: Tokenize,
287 C: Tokenize,
288 R: Detokenize,
289 {
290 fn to_topic<A: Tokenize>(x: A) -> ethabi::Topic<ethabi::Token> {
291 let tokens = x.into_tokens();
292 if tokens.is_empty() {
293 ethabi::Topic::Any
294 } else {
295 tokens.into()
296 }
297 }
298
299 let res = self.abi.event(event).and_then(|ev| {
300 let filter = ev.filter(ethabi::RawTopicFilter {
301 topic0: to_topic(topic0),
302 topic1: to_topic(topic1),
303 topic2: to_topic(topic2),
304 })?;
305 Ok((ev.clone(), filter))
306 });
307 let (ev, filter) = match res {
308 Ok(x) => x,
309 Err(e) => return Err(e.into()),
310 };
311
312 let logs = self
313 .eth
314 .logs(FilterBuilder::default().topic_filter(filter).build())
315 .await?;
316 logs.into_iter()
317 .map(move |l| {
318 let log = ev.parse_log(ethabi::RawLog {
319 topics: l.topics,
320 data: l.data.0,
321 })?;
322
323 R::from_tokens(log.params.into_iter().map(|x| x.value).collect::<Vec<_>>())
324 })
325 .collect::<Result<Vec<R>>>()
326 }
327}
328
329mod contract_signing {
331 use super::*;
332 use crate::{
333 api::Accounts,
334 types::{SignedTransaction, TransactionParameters},
335 };
336
337 impl<T: Transport> Contract<T> {
338 pub async fn sign(
339 &self,
340 func: &str,
341 params: impl Tokenize,
342 options: Options,
343 from: String,
344 key_info: KeyInfo,
345 chain_id: u64,
346 ) -> crate::Result<SignedTransaction> {
347 let fn_data = self
348 .abi
349 .function(func)
350 .and_then(|function| function.encode_input(¶ms.into_tokens()))
351 .map_err(|err| crate::error::Error::Decoder(format!("{:?}", err)))?;
354 let accounts = Accounts::new(self.eth.transport().clone());
355 let mut tx = TransactionParameters {
356 nonce: options.nonce,
357 to: Some(self.address),
358 gas_price: options.gas_price,
359 data: Bytes(fn_data),
360 transaction_type: options.transaction_type,
361 access_list: options.access_list,
362 max_fee_per_gas: options.max_fee_per_gas,
363 max_priority_fee_per_gas: options.max_priority_fee_per_gas,
364 ..Default::default()
365 };
366 if let Some(gas) = options.gas {
367 tx.gas = gas;
368 }
369 if let Some(value) = options.value {
370 tx.value = value;
371 }
372 accounts.sign_transaction(tx, from, key_info, chain_id).await
373 }
374
375 pub async fn signed_call(
380 &self,
381 func: &str,
382 params: impl Tokenize,
383 options: Options,
384 from: String,
385 key_info: KeyInfo,
386 chain_id: u64,
387 ) -> crate::Result<H256> {
388 let signed = self.sign(func, params, options, from, key_info, chain_id).await?;
389 self.eth.send_raw_transaction(signed.raw_transaction).await
390 }
391
392 pub async fn signed_call_with_confirmations(
397 &self,
398 func: &str,
399 params: impl Tokenize,
400 options: Options,
401 from: String,
402 confirmations: usize,
403 key_info: KeyInfo,
404 chain_id: u64,
405 ) -> crate::Result<TransactionReceipt> {
406 let poll_interval = time::Duration::from_secs(1);
407 let signed = self.sign(func, params, options, from, key_info, chain_id).await?;
408
409 confirm::send_raw_transaction_with_confirmation(
410 self.eth.transport().clone(),
411 signed.raw_transaction,
412 poll_interval,
413 confirmations,
414 )
415 .await
416 }
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::{Contract, Options};
423 use crate::{
424 api::{self, Namespace},
425 rpc,
426 transports::test::TestTransport,
427 types::{Address, BlockId, BlockNumber, H256, U256},
428 Transport,
429 };
430
431 fn contract<T: Transport>(transport: &T) -> Contract<&T> {
432 let eth = api::Eth::new(transport);
433 Contract::from_json(eth, Address::from_low_u64_be(1), include_bytes!("./res/token.json")).unwrap()
434 }
435
436 #[test]
437 fn should_call_constant_function() {
438 let mut transport = TestTransport::default();
440 transport.set_response(rpc::Value::String("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000".into()));
441
442 let result: String = {
443 let token = contract(&transport);
444
445 futures::executor::block_on(token.query(
447 "name",
448 (),
449 None,
450 Options::default(),
451 BlockId::Number(BlockNumber::Number(1.into())),
452 ))
453 .unwrap()
454 };
455
456 transport.assert_request(
458 "eth_call",
459 &[
460 "{\"data\":\"0x06fdde03\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into(),
461 "\"0x1\"".into(),
462 ],
463 );
464 transport.assert_no_more_requests();
465 assert_eq!(result, "Hello World!".to_owned());
466 }
467
468 #[test]
469 fn should_call_constant_function_by_hash() {
470 let mut transport = TestTransport::default();
472 transport.set_response(rpc::Value::String("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000".into()));
473
474 let result: String = {
475 let token = contract(&transport);
476
477 futures::executor::block_on(token.query(
479 "name",
480 (),
481 None,
482 Options::default(),
483 BlockId::Hash(H256::default()),
484 ))
485 .unwrap()
486 };
487
488 transport.assert_request(
490 "eth_call",
491 &[
492 "{\"data\":\"0x06fdde03\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into(),
493 "{\"blockHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}".into(),
494 ],
495 );
496 transport.assert_no_more_requests();
497 assert_eq!(result, "Hello World!".to_owned());
498 }
499
500 #[test]
501 fn should_query_with_params() {
502 let mut transport = TestTransport::default();
504 transport.set_response(rpc::Value::String("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000".into()));
505
506 let result: String = {
507 let token = contract(&transport);
508
509 futures::executor::block_on(token.query(
511 "name",
512 (),
513 Address::from_low_u64_be(5),
514 Options::with(|options| {
515 options.gas_price = Some(10_000_000.into());
516 }),
517 BlockId::Number(BlockNumber::Latest),
518 ))
519 .unwrap()
520 };
521
522 transport.assert_request("eth_call", &["{\"data\":\"0x06fdde03\",\"from\":\"0x0000000000000000000000000000000000000005\",\"gasPrice\":\"0x989680\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into(), "\"latest\"".into()]);
524 transport.assert_no_more_requests();
525 assert_eq!(result, "Hello World!".to_owned());
526 }
527
528 #[test]
529 fn should_call_a_contract_function() {
530 let mut transport = TestTransport::default();
532 transport.set_response(rpc::Value::String(format!("{:?}", H256::from_low_u64_be(5))));
533
534 let result = {
535 let token = contract(&transport);
536
537 futures::executor::block_on(token.call("name", (), Address::from_low_u64_be(5), Options::default()))
539 .unwrap()
540 };
541
542 transport.assert_request("eth_sendTransaction", &["{\"data\":\"0x06fdde03\",\"from\":\"0x0000000000000000000000000000000000000005\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into()]);
544 transport.assert_no_more_requests();
545 assert_eq!(result, H256::from_low_u64_be(5));
546 }
547
548 #[test]
549 fn should_estimate_gas_usage() {
550 let mut transport = TestTransport::default();
552 transport.set_response(rpc::Value::String(format!("{:#x}", U256::from(5))));
553
554 let result = {
555 let token = contract(&transport);
556
557 futures::executor::block_on(token.estimate_gas("name", (), Address::from_low_u64_be(5), Options::default()))
559 .unwrap()
560 };
561
562 transport.assert_request("eth_estimateGas", &["{\"data\":\"0x06fdde03\",\"from\":\"0x0000000000000000000000000000000000000005\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into()]);
564 transport.assert_no_more_requests();
565 assert_eq!(result, 5.into());
566 }
567
568 #[test]
569 fn should_query_single_parameter_function() {
570 let mut transport = TestTransport::default();
572 transport.set_response(rpc::Value::String(
573 "0x0000000000000000000000000000000000000000000000000000000000000020".into(),
574 ));
575
576 let result: U256 = {
577 let token = contract(&transport);
578
579 futures::executor::block_on(token.query(
581 "balanceOf",
582 Address::from_low_u64_be(5),
583 None,
584 Options::default(),
585 None,
586 ))
587 .unwrap()
588 };
589
590 transport.assert_request("eth_call", &["{\"data\":\"0x70a082310000000000000000000000000000000000000000000000000000000000000005\",\"to\":\"0x0000000000000000000000000000000000000001\"}".into(), "\"latest\"".into()]);
592 transport.assert_no_more_requests();
593 assert_eq!(result, 0x20.into());
594 }
595}