ethers_middleware/transformer/ds_proxy/
mod.rs

1pub mod factory;
2use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};
3
4use super::{Transformer, TransformerError};
5use ethers_contract::{builders::ContractCall, BaseContract, ContractError};
6use ethers_core::{
7    abi::parse_abi,
8    types::{transaction::eip2718::TypedTransaction, *},
9    utils::id,
10};
11use ethers_providers::Middleware;
12use std::sync::Arc;
13
14/// The function signature of DsProxy's execute function, to execute data on a target address.
15const DS_PROXY_EXECUTE_TARGET: &str =
16    "function execute(address target, bytes memory data) public payable returns (bytes memory response)";
17/// The function signature of DsProxy's execute function, to deploy bytecode and execute data on it.
18const DS_PROXY_EXECUTE_CODE: &str =
19    "function execute(bytes memory code, bytes memory data) public payable returns (address target, bytes memory response)";
20
21/// Represents the DsProxy type that implements the [Transformer] trait.
22///
23/// # Example
24///
25/// ```no_run
26/// use ethers_middleware::{SignerMiddleware, transformer::DsProxy};
27/// use ethers_signers::LocalWallet;
28/// use ethers_providers::{Provider, Http};
29/// use ethers_core::types::{Address, Bytes};
30/// use std::{convert::TryFrom, sync::Arc};
31///
32/// type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
33///
34/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
35/// // instantiate client that can sign transactions.
36/// let wallet: LocalWallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
37///     .parse()?;
38/// let provider = Provider::<Http>::try_from("http://localhost:8545")?;
39/// let client = SignerMiddleware::new(provider, wallet);
40///
41/// # let ds_proxy_addr = Address::random();
42/// // instantiate DsProxy by providing its address.
43/// let ds_proxy = DsProxy::new(ds_proxy_addr);
44///
45/// // execute a transaction via the DsProxy instance.
46/// let target = Address::random();
47/// let calldata: Bytes = vec![0u8; 32].into();
48/// let contract_call = ds_proxy.execute::<HttpWallet, Arc<HttpWallet>, Address>(
49///     Arc::new(client),
50///     target,
51///     calldata,
52/// )?;
53/// let pending_tx = contract_call.send().await?;
54/// let _tx_receipt = pending_tx.await?;
55///
56/// # Ok(())
57/// # }
58/// ```
59#[derive(Clone, Debug)]
60pub struct DsProxy {
61    address: Address,
62    contract: BaseContract,
63}
64
65impl DsProxy {
66    /// Create a new instance of DsProxy by providing the address of the DsProxy contract that has
67    /// already been deployed to the Ethereum network.
68    pub fn new(address: Address) -> Self {
69        let contract = parse_abi(&[DS_PROXY_EXECUTE_TARGET, DS_PROXY_EXECUTE_CODE])
70            .expect("could not parse ABI")
71            .into();
72
73        Self { address, contract }
74    }
75
76    /// The address of the DsProxy instance.
77    pub fn address(&self) -> Address {
78        self.address
79    }
80}
81
82impl DsProxy {
83    /// Deploys a new DsProxy contract to the Ethereum network.
84    pub async fn build<M: Middleware, C: Into<Arc<M>>>(
85        client: C,
86        factory: Option<Address>,
87        owner: Address,
88    ) -> Result<Self, ContractError<M>> {
89        let client = client.into();
90
91        // Fetch chain id and the corresponding address of DsProxyFactory contract
92        // preference is given to DsProxyFactory contract's address if provided
93        // otherwise check the address book for the client's chain ID.
94        let factory: Address = match factory {
95            Some(addr) => addr,
96            None => {
97                let chain_id =
98                    client.get_chainid().await.map_err(ContractError::from_middleware_error)?;
99                match ADDRESS_BOOK.get(&chain_id) {
100                    Some(addr) => *addr,
101                    None => panic!(
102                        "Must either be a supported Network ID or provide DsProxyFactory contract address"
103                    ),
104                }
105            }
106        };
107
108        // broadcast the tx to deploy a new DsProxy.
109        let ds_proxy_factory = DsProxyFactory::new(factory, client);
110        let tx_receipt = ds_proxy_factory
111            .build(owner)
112            .legacy()
113            .send()
114            .await?
115            .await?
116            .ok_or(ContractError::ContractNotDeployed)?;
117
118        // decode the event log to get the address of the deployed contract.
119        if tx_receipt.status == Some(U64::from(1u64)) {
120            // fetch the appropriate log. Only one event is logged by the DsProxyFactory contract,
121            // the others are logged by the deployed DsProxy contract and hence can be ignored.
122            let log = tx_receipt
123                .logs
124                .iter()
125                .find(|i| i.address == factory)
126                .ok_or(ContractError::ContractNotDeployed)?;
127
128            // decode the log.
129            let created_filter: CreatedFilter =
130                ds_proxy_factory.decode_event("Created", log.topics.clone(), log.data.clone())?;
131
132            // instantiate the ABI and return.
133            let contract = parse_abi(&[DS_PROXY_EXECUTE_TARGET, DS_PROXY_EXECUTE_CODE])
134                .expect("could not parse ABI")
135                .into();
136            Ok(Self { address: created_filter.proxy, contract })
137        } else {
138            Err(ContractError::ContractNotDeployed)
139        }
140    }
141}
142
143impl DsProxy {
144    /// Execute a tx through the DsProxy instance. The target can either be a deployed smart
145    /// contract's address, or bytecode of a compiled smart contract. Depending on the target, the
146    /// appropriate `execute` method is called, that is, either
147    /// [execute(address,bytes)](https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L53-L58)
148    /// or [execute(bytes,bytes)](https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L39-L42).
149    pub fn execute<M: Middleware, C: Into<Arc<M>>, T: Into<AddressOrBytes>>(
150        &self,
151        client: C,
152        target: T,
153        data: Bytes,
154    ) -> Result<ContractCall<M, Bytes>, ContractError<M>> {
155        // construct the full contract using DsProxy's address and the injected client.
156        let ds_proxy = self.contract.clone().into_contract(self.address, client.into());
157
158        match target.into() {
159            // handle the case when the target is an address to a deployed contract.
160            AddressOrBytes::Address(addr) => {
161                let selector = id("execute(address,bytes)");
162                let args = (addr, data);
163                Ok(ds_proxy.method_hash(selector, args)?)
164            }
165            // handle the case when the target is actually bytecode of a contract to be deployed
166            // and executed on.
167            AddressOrBytes::Bytes(code) => {
168                let selector = id("execute(bytes,bytes)");
169                let args = (code, data);
170                Ok(ds_proxy.method_hash(selector, args)?)
171            }
172        }
173    }
174}
175
176impl Transformer for DsProxy {
177    fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
178        // the target address cannot be None.
179        let target =
180            *tx.to_addr().ok_or_else(|| TransformerError::MissingField("to".to_string()))?;
181
182        // fetch the data field.
183        let data = tx.data().cloned().unwrap_or_else(|| vec![].into());
184
185        // encode data as the ABI encoded data for DSProxy's execute method.
186        let selector = id("execute(address,bytes)");
187        let encoded_data = self.contract.encode_with_selector(selector, (target, data))?;
188
189        // update appropriate fields of the proxy tx.
190        tx.set_data(encoded_data);
191        tx.set_to(self.address);
192
193        Ok(())
194    }
195}