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