ethcontract/
contract.rs

1//! Abstraction for interacting with ethereum smart contracts. Provides methods
2//! for sending transactions to contracts as well as querying current contract
3//! state.
4
5mod deploy;
6mod event;
7mod method;
8
9use crate::{
10    errors::{DeployError, LinkError},
11    tokens::Tokenize,
12};
13use ethcontract_common::hash::H32;
14use ethcontract_common::{
15    abi::{encode, Error as AbiError, Result as AbiResult},
16    contract::Interface,
17};
18use ethcontract_common::{Abi, Bytecode, Contract, DeploymentInformation};
19use std::hash::Hash;
20use std::sync::Arc;
21use web3::api::Web3;
22use web3::types::{Address, Bytes, H256};
23use web3::Transport;
24
25pub use self::deploy::{Deploy, DeployBuilder};
26pub use self::event::{
27    AllEventsBuilder, Event, EventBuilder, EventMetadata, EventStatus, ParseLog, RawLog,
28    StreamEvent, Topic,
29};
30pub use self::method::{MethodBuilder, MethodDefaults, ViewMethodBuilder};
31use std::marker::PhantomData;
32
33/// Method signature with additional info about method's input and output types.
34///
35/// Additional type parameters are used to help with type inference
36/// for instance's [`method`] and [`view_method`] functions.
37///
38/// [`method`]: `Instance::method`
39/// [`view_method`]: `Instance::view_method`
40#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
41pub struct Signature<P, R>(pub H32, pub std::marker::PhantomData<(P, R)>);
42
43impl<P, R> Signature<P, R> {
44    /// Wraps raw signature.
45    pub fn new(signature: H32) -> Self {
46        Signature(signature, PhantomData)
47    }
48
49    /// Unwraps raw signature.
50    pub fn into_inner(self) -> H32 {
51        self.0
52    }
53}
54
55impl<P, R> From<H32> for Signature<P, R> {
56    fn from(signature: H32) -> Self {
57        Signature::new(signature)
58    }
59}
60
61/// Represents a contract instance at an address. Provides methods for
62/// contract interaction.
63#[derive(Debug, Clone)]
64pub struct Instance<T: Transport> {
65    web3: Web3<T>,
66    address: Address,
67    deployment_information: Option<DeploymentInformation>,
68    /// Default method parameters to use when sending method transactions or
69    /// querying method calls.
70    pub defaults: MethodDefaults,
71    interface: Arc<Interface>,
72}
73
74impl<T: Transport> Instance<T> {
75    /// Creates a new contract instance with the specified `web3` provider with
76    /// the given `Abi` at the given `Address`.
77    ///
78    /// Note that this does not verify that a contract with a matching `Abi` is
79    /// actually deployed at the given address.
80    pub fn at(web3: Web3<T>, interface: Arc<Interface>, address: Address) -> Self {
81        Instance::with_deployment_info(web3, interface, address, None)
82    }
83
84    /// Creates a new contract instance with the specified `web3` provider with
85    /// the given `Abi` at the given `Address` and an optional transaction hash.
86    /// This hash is used to retrieve contract related information such as the
87    /// creation block (which is useful for fetching all historic events).
88    ///
89    /// Note that this does not verify that a contract with a matching `Abi` is
90    /// actually deployed at the given address nor that the transaction hash,
91    /// when provided, is actually for this contract deployment.
92    pub fn with_deployment_info(
93        web3: Web3<T>,
94        interface: Arc<Interface>,
95        address: Address,
96        deployment_information: Option<DeploymentInformation>,
97    ) -> Self {
98        Instance {
99            web3,
100            interface,
101            address,
102            deployment_information,
103            defaults: Default::default(),
104        }
105    }
106
107    /// Locates a deployed contract based on the current network ID reported by
108    /// the `web3` provider from the given `Contract`'s ABI and networks.
109    ///
110    /// Note that this does not verify that a contract with a matching `Abi` is
111    /// actually deployed at the given address.
112    pub async fn deployed(web3: Web3<T>, contract: Contract) -> Result<Self, DeployError> {
113        let network_id = web3.eth().chain_id().await?.to_string();
114        let network = contract
115            .networks
116            .get(&network_id)
117            .ok_or(DeployError::NotFound(network_id))?;
118
119        Ok(Instance::with_deployment_info(
120            web3,
121            contract.interface,
122            network.address,
123            network.deployment_information,
124        ))
125    }
126
127    /// Creates a contract builder with the specified `web3` provider and the
128    /// given `Contract` byte code. This allows the contract deployment
129    /// transaction to be configured before deploying the contract.
130    pub fn builder<P>(
131        web3: Web3<T>,
132        contract: Contract,
133        params: P,
134    ) -> Result<DeployBuilder<T, Self>, DeployError>
135    where
136        P: Tokenize,
137    {
138        Linker::new(contract).deploy(web3, params)
139    }
140
141    /// Deploys a contract with the specified `web3` provider with the given
142    /// `Contract` byte code and linking libraries.
143    pub fn link_and_deploy<'a, P, I>(
144        web3: Web3<T>,
145        contract: Contract,
146        params: P,
147        libraries: I,
148    ) -> Result<DeployBuilder<T, Self>, DeployError>
149    where
150        P: Tokenize,
151        I: Iterator<Item = (&'a str, Address)>,
152    {
153        let mut linker = Linker::new(contract);
154        for (name, address) in libraries {
155            linker = linker.library(name, address)?;
156        }
157
158        linker.deploy(web3, params)
159    }
160
161    /// Retrieve the underlying web3 provider used by this contract instance.
162    pub fn web3(&self) -> Web3<T> {
163        self.web3.clone()
164    }
165
166    /// Retrieves the contract ABI for this instance.
167    pub fn abi(&self) -> &Abi {
168        &self.interface.abi
169    }
170
171    /// Returns the contract address being used by this instance.
172    pub fn address(&self) -> Address {
173        self.address
174    }
175
176    /// Returns the hash for the transaction that deployed the contract if it is
177    /// known, `None` otherwise.
178    pub fn deployment_information(&self) -> Option<DeploymentInformation> {
179        self.deployment_information
180    }
181
182    /// Returns a method builder to setup a call or transaction on a smart
183    /// contract method. Note that calls just get evaluated on a node but do not
184    /// actually commit anything to the block chain.
185    pub fn method<P, R>(
186        &self,
187        signature: impl Into<Signature<P, R>>,
188        params: P,
189    ) -> AbiResult<MethodBuilder<T, R>>
190    where
191        P: Tokenize,
192        R: Tokenize,
193    {
194        let signature = signature.into().into_inner();
195        let function = self
196            .interface
197            .methods
198            .get(&signature)
199            .map(|(name, index)| &self.interface.abi.functions[name][*index])
200            .ok_or_else(|| AbiError::InvalidName(hex::encode(signature)))?;
201        let tokens = match params.into_token() {
202            ethcontract_common::abi::Token::Tuple(tokens) => tokens,
203            _ => unreachable!("function arguments are always tuples"),
204        };
205
206        let data = signature.iter().copied().chain(encode(&tokens)).collect();
207
208        // take ownership here as it greatly simplifies dealing with futures
209        // lifetime as it would require the contract Instance to live until
210        // the end of the future
211        let function = function.clone();
212        let data = Bytes(data);
213
214        Ok(
215            MethodBuilder::new(self.web3(), function, self.address, data)
216                .with_defaults(&self.defaults),
217        )
218    }
219
220    /// Returns a view method builder to setup a call to a smart contract. View
221    /// method builders can't actually send transactions and only query contract
222    /// state.
223    pub fn view_method<P, R>(
224        &self,
225        signature: impl Into<Signature<P, R>>,
226        params: P,
227    ) -> AbiResult<ViewMethodBuilder<T, R>>
228    where
229        P: Tokenize,
230        R: Tokenize,
231    {
232        Ok(self.method(signature, params)?.view())
233    }
234
235    /// Returns a method builder to setup a call to a smart contract's fallback
236    /// function.
237    ///
238    /// This method will error if the ABI does not contain an entry for a
239    /// fallback function.
240    pub fn fallback<D>(&self, data: D) -> AbiResult<MethodBuilder<T, ()>>
241    where
242        D: Into<Vec<u8>>,
243    {
244        if !self.interface.abi.fallback && !self.interface.abi.receive {
245            return Err(AbiError::InvalidName("fallback".into()));
246        }
247
248        Ok(MethodBuilder::fallback(
249            self.web3(),
250            self.address,
251            Bytes(data.into()),
252        ))
253    }
254
255    /// Returns a event builder to setup an event stream for a smart contract
256    /// that emits events for the specified Solidity event by name.
257    pub fn event<E>(&self, signature: H256) -> AbiResult<EventBuilder<T, E>>
258    where
259        E: Tokenize,
260    {
261        let event = self
262            .interface
263            .events
264            .get(&signature)
265            .map(|(name, index)| &self.interface.abi.events[name][*index])
266            .ok_or_else(|| AbiError::InvalidName(hex::encode(signature)))?;
267
268        Ok(EventBuilder::new(
269            self.web3(),
270            event.clone(),
271            self.address(),
272        ))
273    }
274
275    /// Returns a log stream that emits a log for every new event emitted after
276    /// the stream was created for this contract instance.
277    pub fn all_events(&self) -> AllEventsBuilder<T, RawLog> {
278        AllEventsBuilder::new(self.web3(), self.address(), self.deployment_information())
279    }
280}
281
282/// Builder for specifying linking options for a contract.
283#[derive(Debug, Clone)]
284pub struct Linker {
285    /// The contract interface.
286    interface: Arc<Interface>,
287    /// The deployment code for the contract.
288    bytecode: Bytecode,
289}
290
291impl Linker {
292    /// Create a new linker for a contract.
293    pub fn new(contract: Contract) -> Linker {
294        Linker {
295            interface: contract.interface,
296            bytecode: contract.bytecode,
297        }
298    }
299
300    /// Specify a linked library used for this contract. Note that we
301    /// incrementally link so that we can verify each time a library is linked
302    /// whether it was successful or not.
303    ///
304    /// # Panics
305    ///
306    /// Panics if an invalid library name is used (for example if it is more
307    /// than 38 characters long).
308    pub fn library<S>(mut self, name: S, address: Address) -> Result<Linker, LinkError>
309    where
310        S: AsRef<str>,
311    {
312        self.bytecode.link(name, address)?;
313        Ok(self)
314    }
315
316    /// Finish linking and check if there are any outstanding unlinked libraries
317    /// and create a deployment builder.
318    pub fn deploy<T, P>(
319        self,
320        web3: Web3<T>,
321        params: P,
322    ) -> Result<DeployBuilder<T, Instance<T>>, DeployError>
323    where
324        T: Transport,
325        P: Tokenize,
326    {
327        DeployBuilder::new(web3, self, params)
328    }
329}
330
331impl<T: Transport> Deploy<T> for Instance<T> {
332    type Context = Linker;
333
334    fn abi(cx: &Self::Context) -> &Abi {
335        &cx.interface.abi
336    }
337
338    fn bytecode(cx: &Self::Context) -> &Bytecode {
339        &cx.bytecode
340    }
341
342    fn from_deployment(
343        web3: Web3<T>,
344        address: Address,
345        transaction_hash: H256,
346        cx: Self::Context,
347    ) -> Self {
348        Instance::with_deployment_info(
349            web3,
350            cx.interface,
351            address,
352            Some(DeploymentInformation::TransactionHash(transaction_hash)),
353        )
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::test::prelude::*;
361    use ethcontract_common::contract::Network;
362
363    #[test]
364    fn deployed() {
365        let mut transport = TestTransport::new();
366        let web3 = Web3::new(transport.clone());
367
368        let address = addr!("0x0102030405060708091011121314151617181920");
369        let contract = {
370            let mut contract = Contract::empty();
371            contract.networks.insert(
372                "42".to_string(),
373                Network {
374                    address,
375                    deployment_information: Some(H256::repeat_byte(0x42).into()),
376                },
377            );
378            contract
379        };
380
381        transport.add_response(json!("0x2a")); // eth_chainId response
382        let instance = Instance::deployed(web3, contract)
383            .immediate()
384            .expect("successful deployment");
385
386        transport.assert_request("eth_chainId", &[]);
387        transport.assert_no_more_requests();
388
389        assert_eq!(instance.address(), address);
390        assert_eq!(
391            instance.deployment_information(),
392            Some(DeploymentInformation::TransactionHash(H256::repeat_byte(
393                0x42
394            )))
395        );
396    }
397
398    #[test]
399    fn deployed_not_found() {
400        let mut transport = TestTransport::new();
401        let web3 = Web3::new(transport.clone());
402
403        transport.add_response(json!("0x2a")); // eth_chainId response
404        let err = Instance::deployed(web3, Contract::empty())
405            .immediate()
406            .expect_err("unexpected success getting deployed contract");
407
408        transport.assert_request("eth_chainId", &[]);
409        transport.assert_no_more_requests();
410
411        assert!(
412            match &err {
413                DeployError::NotFound(id) => id == "42",
414                _ => false,
415            },
416            "expected network 42 not found error but got '{:?}'",
417            err
418        );
419    }
420}