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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
pub mod factory;
use factory::{CreatedFilter, DsProxyFactory, ADDRESS_BOOK};

use super::{Transformer, TransformerError};
use ethers_contract::{builders::ContractCall, BaseContract, ContractError};
use ethers_core::{
    abi::parse_abi,
    types::{transaction::eip2718::TypedTransaction, *},
    utils::id,
};
use ethers_providers::Middleware;
use std::sync::Arc;

/// The function signature of DsProxy's execute function, to execute data on a target address.
const DS_PROXY_EXECUTE_TARGET: &str =
    "function execute(address target, bytes memory data) public payable returns (bytes memory response)";
/// The function signature of DsProxy's execute function, to deploy bytecode and execute data on it.
const DS_PROXY_EXECUTE_CODE: &str =
    "function execute(bytes memory code, bytes memory data) public payable returns (address target, bytes memory response)";

/// Represents the DsProxy type that implements the [Transformer] trait.
///
/// # Example
///
/// ```no_run
/// use ethers_middleware::{SignerMiddleware, transformer::DsProxy};
/// use ethers_signers::LocalWallet;
/// use ethers_providers::{Provider, Http};
/// use ethers_core::types::{Address, Bytes};
/// use std::{convert::TryFrom, sync::Arc};
///
/// type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
///
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// // instantiate client that can sign transactions.
/// let wallet: LocalWallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
///     .parse()?;
/// let provider = Provider::<Http>::try_from("http://localhost:8545")?;
/// let client = SignerMiddleware::new(provider, wallet);
///
/// # let ds_proxy_addr = Address::random();
/// // instantiate DsProxy by providing its address.
/// let ds_proxy = DsProxy::new(ds_proxy_addr);
///
/// // execute a transaction via the DsProxy instance.
/// let target = Address::random();
/// let calldata: Bytes = vec![0u8; 32].into();
/// let contract_call = ds_proxy.execute::<HttpWallet, Arc<HttpWallet>, Address>(
///     Arc::new(client),
///     target,
///     calldata,
/// )?;
/// let pending_tx = contract_call.send().await?;
/// let _tx_receipt = pending_tx.await?;
///
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct DsProxy {
    address: Address,
    contract: BaseContract,
}

impl DsProxy {
    /// Create a new instance of DsProxy by providing the address of the DsProxy contract that has
    /// already been deployed to the Ethereum network.
    pub fn new(address: Address) -> Self {
        let contract = parse_abi(&[DS_PROXY_EXECUTE_TARGET, DS_PROXY_EXECUTE_CODE])
            .expect("could not parse ABI")
            .into();

        Self { address, contract }
    }

    /// The address of the DsProxy instance.
    pub fn address(&self) -> Address {
        self.address
    }
}

impl DsProxy {
    /// Deploys a new DsProxy contract to the Ethereum network.
    pub async fn build<M: Middleware, C: Into<Arc<M>>>(
        client: C,
        factory: Option<Address>,
        owner: Address,
    ) -> Result<Self, ContractError<M>> {
        let client = client.into();

        // Fetch chain id and the corresponding address of DsProxyFactory contract
        // preference is given to DsProxyFactory contract's address if provided
        // otherwise check the address book for the client's chain ID.
        let factory: Address = match factory {
            Some(addr) => addr,
            None => {
                let chain_id =
                    client.get_chainid().await.map_err(ContractError::from_middleware_error)?;
                match ADDRESS_BOOK.get(&chain_id) {
                    Some(addr) => *addr,
                    None => panic!(
                        "Must either be a supported Network ID or provide DsProxyFactory contract address"
                    ),
                }
            }
        };

        // broadcast the tx to deploy a new DsProxy.
        let ds_proxy_factory = DsProxyFactory::new(factory, client);
        let tx_receipt = ds_proxy_factory
            .build(owner)
            .legacy()
            .send()
            .await?
            .await?
            .ok_or(ContractError::ContractNotDeployed)?;

        // decode the event log to get the address of the deployed contract.
        if tx_receipt.status == Some(U64::from(1u64)) {
            // fetch the appropriate log. Only one event is logged by the DsProxyFactory contract,
            // the others are logged by the deployed DsProxy contract and hence can be ignored.
            let log = tx_receipt
                .logs
                .iter()
                .find(|i| i.address == factory)
                .ok_or(ContractError::ContractNotDeployed)?;

            // decode the log.
            let created_filter: CreatedFilter =
                ds_proxy_factory.decode_event("Created", log.topics.clone(), log.data.clone())?;

            // instantiate the ABI and return.
            let contract = parse_abi(&[DS_PROXY_EXECUTE_TARGET, DS_PROXY_EXECUTE_CODE])
                .expect("could not parse ABI")
                .into();
            Ok(Self { address: created_filter.proxy, contract })
        } else {
            Err(ContractError::ContractNotDeployed)
        }
    }
}

impl DsProxy {
    /// Execute a tx through the DsProxy instance. The target can either be a deployed smart
    /// contract's address, or bytecode of a compiled smart contract. Depending on the target, the
    /// appropriate `execute` method is called, that is, either
    /// [execute(address,bytes)](https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L53-L58)
    /// or [execute(bytes,bytes)](https://github.com/dapphub/ds-proxy/blob/master/src/proxy.sol#L39-L42).
    pub fn execute<M: Middleware, C: Into<Arc<M>>, T: Into<AddressOrBytes>>(
        &self,
        client: C,
        target: T,
        data: Bytes,
    ) -> Result<ContractCall<M, Bytes>, ContractError<M>> {
        // construct the full contract using DsProxy's address and the injected client.
        let ds_proxy = self.contract.clone().into_contract(self.address, client.into());

        match target.into() {
            // handle the case when the target is an address to a deployed contract.
            AddressOrBytes::Address(addr) => {
                let selector = id("execute(address,bytes)");
                let args = (addr, data);
                Ok(ds_proxy.method_hash(selector, args)?)
            }
            // handle the case when the target is actually bytecode of a contract to be deployed
            // and executed on.
            AddressOrBytes::Bytes(code) => {
                let selector = id("execute(bytes,bytes)");
                let args = (code, data);
                Ok(ds_proxy.method_hash(selector, args)?)
            }
        }
    }
}

impl Transformer for DsProxy {
    fn transform(&self, tx: &mut TypedTransaction) -> Result<(), TransformerError> {
        // the target address cannot be None.
        let target =
            *tx.to_addr().ok_or_else(|| TransformerError::MissingField("to".to_string()))?;

        // fetch the data field.
        let data = tx.data().cloned().unwrap_or_else(|| vec![].into());

        // encode data as the ABI encoded data for DSProxy's execute method.
        let selector = id("execute(address,bytes)");
        let encoded_data = self.contract.encode_with_selector(selector, (target, data))?;

        // update appropriate fields of the proxy tx.
        tx.set_data(encoded_data);
        tx.set_to(self.address);

        Ok(())
    }
}