1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use crate::{CallBuilder, Event, Interface, Result};
use alloy_dyn_abi::DynSolValue;
use alloy_json_abi::{Function, JsonAbi};
use alloy_network::{Ethereum, Network};
use alloy_primitives::{Address, Selector};
use alloy_provider::Provider;
use alloy_rpc_types_eth::Filter;
use alloy_sol_types::SolEvent;
use alloy_transport::Transport;
use std::marker::PhantomData;

/// A handle to an Ethereum contract at a specific address.
///
/// A contract is an abstraction of an executable program on Ethereum. Every deployed contract has
/// an address, which is used to connect to it so that it may receive messages (transactions).
#[derive(Clone)]
pub struct ContractInstance<T, P, N = Ethereum> {
    address: Address,
    provider: P,
    interface: Interface,
    transport: PhantomData<T>,
    network: PhantomData<N>,
}

impl<T, P, N> ContractInstance<T, P, N> {
    /// Creates a new contract from the provided address, provider, and interface.
    #[inline]
    pub const fn new(address: Address, provider: P, interface: Interface) -> Self {
        Self { address, provider, interface, transport: PhantomData, network: PhantomData }
    }

    /// Returns a reference to the contract's address.
    #[inline]
    pub const fn address(&self) -> &Address {
        &self.address
    }

    /// Sets the contract's address.
    #[inline]
    pub fn set_address(&mut self, address: Address) {
        self.address = address;
    }

    /// Returns a new contract instance at `address`.
    #[inline]
    pub fn at(mut self, address: Address) -> Self {
        self.set_address(address);
        self
    }

    /// Returns a reference to the contract's ABI.
    #[inline]
    pub const fn abi(&self) -> &JsonAbi {
        self.interface.abi()
    }

    /// Returns a reference to the contract's provider.
    #[inline]
    pub const fn provider(&self) -> &P {
        &self.provider
    }
}

impl<T, P: Clone, N> ContractInstance<T, &P, N> {
    /// Clones the provider and returns a new contract instance with the cloned provider.
    #[inline]
    pub fn with_cloned_provider(self) -> ContractInstance<T, P, N> {
        ContractInstance {
            address: self.address,
            provider: self.provider.clone(),
            interface: self.interface,
            transport: PhantomData,
            network: PhantomData,
        }
    }
}

impl<T: Transport + Clone, P: Provider<T, N>, N: Network> ContractInstance<T, P, N> {
    /// Returns a transaction builder for the provided function name.
    ///
    /// If there are multiple functions with the same name due to overloading, consider using
    /// the [`ContractInstance::function_from_selector`] method instead, since this will use the
    /// first match.
    pub fn function(
        &self,
        name: &str,
        args: &[DynSolValue],
    ) -> Result<CallBuilder<T, &P, Function, N>> {
        let function = self.interface.get_from_name(name)?;
        CallBuilder::new_dyn(&self.provider, &self.address, function, args)
    }

    /// Returns a transaction builder for the provided function selector.
    pub fn function_from_selector(
        &self,
        selector: &Selector,
        args: &[DynSolValue],
    ) -> Result<CallBuilder<T, &P, Function, N>> {
        let function = self.interface.get_from_selector(selector)?;
        CallBuilder::new_dyn(&self.provider, &self.address, function, args)
    }

    /// Returns an [`Event`] builder with the provided filter.
    pub const fn event<E: SolEvent>(&self, filter: Filter) -> Event<T, &P, E, N> {
        Event::new(&self.provider, filter)
    }
}

impl<T, P, N> std::ops::Deref for ContractInstance<T, P, N> {
    type Target = Interface;

    fn deref(&self) -> &Self::Target {
        &self.interface
    }
}

impl<T, P, N> std::fmt::Debug for ContractInstance<T, P, N> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ContractInstance").field("address", &self.address).finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloy_network::TransactionBuilder;
    use alloy_primitives::{hex, U256};
    use alloy_provider::ProviderBuilder;
    use alloy_rpc_types_eth::TransactionRequest;

    #[tokio::test]
    async fn contract_interface() {
        let provider = ProviderBuilder::new().on_anvil();

        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"}]"#;
        let abi = serde_json::from_str::<JsonAbi>(abi_str).unwrap();
        let bytecode = hex::decode("6080806040523460135760b2908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c90816361bc221a146065575063d09de08a14602f575f80fd5b346061575f3660031901126061575f5460018101809111604d575f55005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b346061575f3660031901126061576020905f548152f3fea2646970667358221220d802267a5f574e54a87a63d0ff8d733fdb275e6e6c502831d9e14f957bbcd7a264736f6c634300081a0033").unwrap();
        let deploy_tx = TransactionRequest::default().with_deploy_code(bytecode);
        let address = provider
            .send_transaction(deploy_tx)
            .await
            .unwrap()
            .get_receipt()
            .await
            .unwrap()
            .contract_address
            .unwrap();

        let contract = ContractInstance::new(address, provider, Interface::new(abi));
        assert_eq!(contract.abi().functions().count(), 2);

        let result = contract.function("counter", &[]).unwrap().call().await.unwrap();
        assert_eq!(result[0].as_uint().unwrap().0, U256::from(0));

        contract.function("increment", &[]).unwrap().send().await.unwrap().watch().await.unwrap();

        let result = contract.function("counter", &[]).unwrap().call().await.unwrap();
        assert_eq!(result[0].as_uint().unwrap().0, U256::from(1));
    }
}