1use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
6use crate::{batch::CallBatch, errors::MethodError, tokens::Tokenize};
7use ethcontract_common::abi::{Function, Token};
8use std::marker::PhantomData;
9use web3::types::{AccessList, Address, BlockId, Bytes, CallRequest, U256};
10use web3::Transport;
11use web3::{api::Web3, BatchTransport};
12
13#[derive(Clone, Debug, Default)]
15pub struct MethodDefaults {
16 pub from: Option<Account>,
18 pub gas: Option<U256>,
20 pub gas_price: Option<GasPrice>,
22}
23
24#[derive(Debug, Clone)]
28#[must_use = "methods do nothing unless you `.call()` or `.send()` them"]
29pub struct MethodBuilder<T: Transport, R: Tokenize> {
30 web3: Web3<T>,
31 function: Function,
32 pub tx: TransactionBuilder<T>,
34 _result: PhantomData<R>,
35}
36
37impl<T: Transport> MethodBuilder<T, ()> {
38 pub fn fallback(web3: Web3<T>, address: Address, data: Bytes) -> Self {
40 #[allow(deprecated)]
44 let function = Function {
45 name: "fallback".into(),
46 inputs: vec![],
47 outputs: vec![],
48 constant: None,
49 state_mutability: Default::default(),
50 };
51 MethodBuilder::new(web3, function, address, data)
52 }
53}
54
55impl<T: Transport, R: Tokenize> MethodBuilder<T, R> {
56 pub fn new(web3: Web3<T>, function: Function, address: Address, data: Bytes) -> Self {
58 MethodBuilder {
59 web3: web3.clone(),
60 function,
61 tx: TransactionBuilder::new(web3).to(address).data(data),
62 _result: PhantomData,
63 }
64 }
65
66 pub fn with_defaults(mut self, defaults: &MethodDefaults) -> Self {
68 self.tx.from = self.tx.from.or_else(|| defaults.from.clone());
69 self.tx.gas = self.tx.gas.or(defaults.gas);
70 self.tx.gas_price = self.tx.gas_price.or(defaults.gas_price);
71 self
72 }
73
74 pub fn function(&self) -> &Function {
76 &self.function
77 }
78
79 pub fn from(mut self, value: Account) -> Self {
82 self.tx = self.tx.from(value);
83 self
84 }
85
86 pub fn gas(mut self, value: U256) -> Self {
89 self.tx = self.tx.gas(value);
90 self
91 }
92
93 pub fn gas_price(mut self, value: GasPrice) -> Self {
96 self.tx = self.tx.gas_price(value);
97 self
98 }
99
100 pub fn value(mut self, value: U256) -> Self {
103 self.tx = self.tx.value(value);
104 self
105 }
106
107 pub fn nonce(mut self, value: U256) -> Self {
110 self.tx = self.tx.nonce(value);
111 self
112 }
113
114 pub fn confirmations(mut self, value: usize) -> Self {
118 self.tx = self.tx.confirmations(value);
119 self
120 }
121
122 pub fn access_list(mut self, value: AccessList) -> Self {
124 self.tx = self.tx.access_list(value);
125 self
126 }
127
128 pub fn into_inner(self) -> TransactionBuilder<T> {
131 self.tx
132 }
133
134 pub async fn send(self) -> Result<TransactionResult, MethodError> {
136 let Self { function, tx, .. } = self;
137 tx.send()
138 .await
139 .map_err(|err| MethodError::new(&function, err))
140 }
141
142 pub fn view(self) -> ViewMethodBuilder<T, R> {
145 ViewMethodBuilder::from_method(self)
146 }
147
148 pub async fn call(self) -> Result<R, MethodError> {
153 self.view().call().await
154 }
155}
156
157#[derive(Debug, Clone)]
160#[must_use = "view methods do nothing unless you `.call()` them"]
161pub struct ViewMethodBuilder<T: Transport, R: Tokenize> {
162 pub m: MethodBuilder<T, R>,
164 pub block: Option<BlockId>,
166}
167
168impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
169 pub fn from_method(method: MethodBuilder<T, R>) -> Self {
171 ViewMethodBuilder {
172 m: method,
173 block: None,
174 }
175 }
176
177 pub fn with_defaults(mut self, defaults: &MethodDefaults) -> Self {
179 self.m = self.m.with_defaults(defaults);
180 self
181 }
182
183 pub fn function(&self) -> &Function {
185 &self.m.function
186 }
187
188 pub fn from(mut self, value: Address) -> Self {
190 self.m = self.m.from(Account::Local(value, None));
191 self
192 }
193
194 pub fn gas(mut self, value: U256) -> Self {
197 self.m = self.m.gas(value);
198 self
199 }
200
201 pub fn gas_price(mut self, value: GasPrice) -> Self {
204 self.m = self.m.gas_price(value);
205 self
206 }
207
208 pub fn value(mut self, value: U256) -> Self {
211 self.m = self.m.value(value);
212 self
213 }
214
215 pub fn nonce(mut self, value: U256) -> Self {
218 self.m = self.m.nonce(value);
219 self
220 }
221
222 pub fn access_list(mut self, value: AccessList) -> Self {
224 self.m = self.m.access_list(value);
225 self
226 }
227
228 pub fn block(mut self, value: BlockId) -> Self {
231 self.block = Some(value);
232 self
233 }
234}
235
236impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
237 pub async fn call(self) -> Result<R, MethodError> {
240 let eth = &self.m.web3.eth();
241 let (function, call, block) = self.decompose();
242 let future = eth.call(call, block);
243 convert_response::<_, R>(future, function).await
244 }
245
246 pub fn batch_call<B: BatchTransport>(
250 self,
251 batch: &mut CallBatch<B>,
252 ) -> impl std::future::Future<Output = Result<R, MethodError>> {
253 let (function, call, block) = self.decompose();
254 let future = batch.push(call, block);
255 async move { convert_response::<_, R>(future, function).await }
256 }
257
258 fn decompose(self) -> (Function, CallRequest, Option<BlockId>) {
259 let resolved_gas_price = self
260 .m
261 .tx
262 .gas_price
263 .map(|gas_price| gas_price.resolve_for_transaction())
264 .unwrap_or_default();
265 (
266 self.m.function,
267 CallRequest {
268 from: self.m.tx.from.map(|account| account.address()),
269 to: self.m.tx.to,
270 gas: self.m.tx.gas,
271 gas_price: resolved_gas_price.gas_price,
272 value: self.m.tx.value,
273 data: self.m.tx.data,
274 transaction_type: resolved_gas_price.transaction_type,
275 access_list: self.m.tx.access_list,
276 max_fee_per_gas: resolved_gas_price.max_fee_per_gas,
277 max_priority_fee_per_gas: resolved_gas_price.max_priority_fee_per_gas,
278 },
279 self.block,
280 )
281 }
282}
283
284async fn convert_response<
285 F: std::future::Future<Output = Result<Bytes, web3::Error>>,
286 R: Tokenize,
287>(
288 future: F,
289 function: Function,
290) -> Result<R, MethodError> {
291 let bytes = future
292 .await
293 .map_err(|err| MethodError::new(&function, err))?;
294 let tokens = function
295 .decode_output(&bytes.0)
296 .map_err(|err| MethodError::new(&function, err))?;
297 let token = match tokens.len() {
298 0 => Token::Tuple(Vec::new()),
299 1 => tokens.into_iter().next().unwrap(),
300 _ => Token::Tuple(tokens),
304 };
305 let result = R::from_token(token).map_err(|err| MethodError::new(&function, err))?;
306 Ok(result)
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::test::prelude::*;
313 use ethcontract_common::abi::{Param, ParamType};
314 use web3::types::AccessListItem;
315
316 fn test_abi_function() -> (Function, Bytes) {
317 #[allow(deprecated)]
318 let function = Function {
319 name: "test".to_owned(),
320 inputs: Vec::new(),
321 outputs: vec![Param {
322 name: "".to_owned(),
323 kind: ParamType::Uint(256),
324 internal_type: None,
325 }],
326 constant: None,
327 state_mutability: Default::default(),
328 };
329 let data = function
330 .encode_input(&[])
331 .expect("error encoding empty input");
332
333 (function, Bytes(data))
334 }
335
336 #[test]
337 fn method_tx_options() {
338 let transport = TestTransport::new();
339 let web3 = Web3::new(transport.clone());
340
341 let address = addr!("0x0123456789012345678901234567890123456789");
342 let from = addr!("0x9876543210987654321098765432109876543210");
343 let (function, data) = test_abi_function();
344 let tx = MethodBuilder::<_, U256>::new(web3, function, address, data.clone())
345 .from(Account::Local(from, None))
346 .gas(1.into())
347 .gas_price(2.0.into())
348 .value(28.into())
349 .nonce(42.into())
350 .access_list(vec![AccessListItem::default()])
351 .into_inner();
352
353 assert_eq!(tx.from.map(|a| a.address()), Some(from));
354 assert_eq!(tx.to, Some(address));
355 assert_eq!(tx.gas, Some(1.into()));
356 assert_eq!(tx.gas_price, Some(2.0.into()));
357 assert_eq!(tx.value, Some(28.into()));
358 assert_eq!(tx.data, Some(data));
359 assert_eq!(tx.nonce, Some(42.into()));
360 assert_eq!(tx.access_list, Some(vec![AccessListItem::default()]));
361 transport.assert_no_more_requests();
362 }
363
364 #[test]
365 fn view_method_call() {
366 let mut transport = TestTransport::new();
367 let web3 = Web3::new(transport.clone());
368
369 let address = addr!("0x0123456789012345678901234567890123456789");
370 let from = addr!("0x9876543210987654321098765432109876543210");
371 let (function, data) = test_abi_function();
372 let tx = ViewMethodBuilder::<_, U256>::from_method(MethodBuilder::new(
373 web3,
374 function,
375 address,
376 data.clone(),
377 ))
378 .from(from)
379 .gas(1.into())
380 .gas_price(2.0.into())
381 .value(28.into())
382 .block(BlockId::Number(100.into()));
383
384 transport.add_response(json!(
385 "0x000000000000000000000000000000000000000000000000000000000000002a"
386 )); let result = tx.call().immediate().expect("call error");
388
389 assert_eq!(result, 42.into());
390 transport.assert_request(
391 "eth_call",
392 &[
393 json!({
394 "from": from,
395 "to": address,
396 "gas": "0x1",
397 "gasPrice": "0x2",
398 "value": "0x1c",
399 "data": data,
400 }),
401 json!("0x64"),
402 ],
403 );
404 transport.assert_no_more_requests();
405 }
406
407 #[test]
408 fn method_to_view_method_preserves_options() {
409 let mut transport = TestTransport::new();
410 let web3 = Web3::new(transport.clone());
411
412 let address = addr!("0x0123456789012345678901234567890123456789");
413 let (function, data) = test_abi_function();
414 let tx = MethodBuilder::<_, U256>::new(web3, function, address, data.clone())
415 .gas(42.into())
416 .view();
417
418 transport.add_response(json!(
419 "0x0000000000000000000000000000000000000000000000000000000000000000"
420 ));
421 tx.call().immediate().expect("call error");
422
423 transport.assert_request(
424 "eth_call",
425 &[
426 json!({
427 "to": address,
428 "gas": "0x2a",
429 "data": data,
430 }),
431 json!("latest"),
432 ],
433 );
434 transport.assert_no_more_requests();
435 }
436
437 #[test]
438 fn method_defaults_are_applied() {
439 let transport = TestTransport::new();
440 let web3 = Web3::new(transport.clone());
441
442 let from = addr!("0x9876543210987654321098765432109876543210");
443 let address = addr!("0x0123456789012345678901234567890123456789");
444 let (function, data) = test_abi_function();
445 let tx = MethodBuilder::<_, U256>::new(web3, function, address, data)
446 .with_defaults(&MethodDefaults {
447 from: Some(Account::Local(from, None)),
448 gas: Some(1.into()),
449 gas_price: Some(2.0.into()),
450 })
451 .into_inner();
452
453 assert_eq!(tx.from.map(|a| a.address()), Some(from));
454 assert_eq!(tx.gas, Some(1.into()));
455 assert_eq!(tx.gas_price, Some(2.0.into()));
456 transport.assert_no_more_requests();
457 }
458}