alloy_contract/
instance.rs

1use crate::{CallBuilder, Event, Interface, Result};
2use alloy_dyn_abi::DynSolValue;
3use alloy_json_abi::{Function, JsonAbi};
4use alloy_network::{Ethereum, Network};
5use alloy_primitives::{Address, Selector};
6use alloy_provider::Provider;
7use alloy_rpc_types_eth::Filter;
8use alloy_sol_types::SolEvent;
9use std::marker::PhantomData;
10
11/// A handle to an Ethereum contract at a specific address.
12///
13/// A contract is an abstraction of an executable program on Ethereum. Every deployed contract has
14/// an address, which is used to connect to it so that it may receive messages (transactions).
15#[derive(Clone)]
16pub struct ContractInstance<P, N = Ethereum> {
17    address: Address,
18    provider: P,
19    interface: Interface,
20    network: PhantomData<N>,
21}
22
23impl<P, N> ContractInstance<P, N> {
24    /// Creates a new contract from the provided address, provider, and interface.
25    #[inline]
26    pub const fn new(address: Address, provider: P, interface: Interface) -> Self {
27        Self { address, provider, interface, network: PhantomData }
28    }
29
30    /// Returns a reference to the contract's address.
31    #[inline]
32    pub const fn address(&self) -> &Address {
33        &self.address
34    }
35
36    /// Sets the contract's address.
37    #[inline]
38    pub fn set_address(&mut self, address: Address) {
39        self.address = address;
40    }
41
42    /// Returns a new contract instance at `address`.
43    #[inline]
44    pub fn at(mut self, address: Address) -> Self {
45        self.set_address(address);
46        self
47    }
48
49    /// Returns a reference to the contract's ABI.
50    #[inline]
51    pub const fn abi(&self) -> &JsonAbi {
52        self.interface.abi()
53    }
54
55    /// Returns a reference to the contract's provider.
56    #[inline]
57    pub const fn provider(&self) -> &P {
58        &self.provider
59    }
60}
61
62impl<P: Clone, N> ContractInstance<&P, N> {
63    /// Clones the provider and returns a new contract instance with the cloned provider.
64    #[inline]
65    pub fn with_cloned_provider(self) -> ContractInstance<P, N> {
66        ContractInstance {
67            address: self.address,
68            provider: self.provider.clone(),
69            interface: self.interface,
70            network: PhantomData,
71        }
72    }
73}
74
75impl<P: Provider<N>, N: Network> ContractInstance<P, N> {
76    /// Returns a transaction builder for the provided function name.
77    ///
78    /// If there are multiple functions with the same name due to overloading, consider using
79    /// the [`ContractInstance::function_from_selector`] method instead, since this will use the
80    /// first match.
81    pub fn function(
82        &self,
83        name: &str,
84        args: &[DynSolValue],
85    ) -> Result<CallBuilder<&P, Function, N>> {
86        let function = self.interface.get_from_name(name)?;
87        CallBuilder::new_dyn(&self.provider, &self.address, function, args)
88    }
89
90    /// Returns a transaction builder for the provided function selector.
91    pub fn function_from_selector(
92        &self,
93        selector: &Selector,
94        args: &[DynSolValue],
95    ) -> Result<CallBuilder<&P, Function, N>> {
96        let function = self.interface.get_from_selector(selector)?;
97        CallBuilder::new_dyn(&self.provider, &self.address, function, args)
98    }
99
100    /// Returns an [`Event`] builder with the provided filter.
101    pub const fn event<E: SolEvent>(&self, filter: Filter) -> Event<&P, E, N> {
102        Event::new(&self.provider, filter)
103    }
104}
105
106impl<P, N> std::ops::Deref for ContractInstance<P, N> {
107    type Target = Interface;
108
109    fn deref(&self) -> &Self::Target {
110        &self.interface
111    }
112}
113
114impl<P, N> std::fmt::Debug for ContractInstance<P, N> {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("ContractInstance").field("address", &self.address).finish()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use alloy_network::TransactionBuilder;
124    use alloy_primitives::{hex, U256};
125    use alloy_provider::ProviderBuilder;
126    use alloy_rpc_types_eth::TransactionRequest;
127
128    #[tokio::test]
129    async fn contract_interface() {
130        let provider = ProviderBuilder::new().connect_anvil_with_wallet();
131
132        let abi_str = r#"[{"inputs":[],"name":"counter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"increment","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#;
133        let abi = serde_json::from_str::<JsonAbi>(abi_str).unwrap();
134        let bytecode = hex::decode("6080806040523460135760b2908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c90816361bc221a146065575063d09de08a14602f575f80fd5b346061575f3660031901126061575f5460018101809111604d575f55005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b346061575f3660031901126061576020905f548152f3fea2646970667358221220d802267a5f574e54a87a63d0ff8d733fdb275e6e6c502831d9e14f957bbcd7a264736f6c634300081a0033").unwrap();
135        let deploy_tx = TransactionRequest::default().with_deploy_code(bytecode);
136        let address = provider
137            .send_transaction(deploy_tx)
138            .await
139            .unwrap()
140            .get_receipt()
141            .await
142            .unwrap()
143            .contract_address
144            .unwrap();
145
146        let contract = ContractInstance::new(address, provider, Interface::new(abi));
147        assert_eq!(contract.abi().functions().count(), 2);
148
149        let result = contract.function("counter", &[]).unwrap().call().await.unwrap();
150        assert_eq!(result[0].as_uint().unwrap().0, U256::from(0));
151
152        contract.function("increment", &[]).unwrap().send().await.unwrap().watch().await.unwrap();
153
154        let result = contract.function("counter", &[]).unwrap().call().await.unwrap();
155        assert_eq!(result[0].as_uint().unwrap().0, U256::from(1));
156    }
157}