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