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}