web3 0.3.0

Ethereum JSON-RPC client.
Documentation
//! Contract deployment utilities

use std::time;
use ethabi;
use futures::{Future, Async, Poll};

use api::{Eth, Namespace};
use confirm;
use contract::tokens::Tokenize;
use contract::{Contract, Options};
use types::{Address, Bytes, TransactionRequest};
use {Transport};

pub use contract::error::deploy::{Error, ErrorKind};

/// A configuration builder for contract deployment.
#[derive(Debug)]
pub struct Builder<T: Transport> {
  pub(crate) eth: Eth<T>,
  pub(crate) abi: ethabi::Contract,
  pub(crate) options: Options,
  pub(crate) confirmations: usize,
  pub(crate) poll_interval: time::Duration,
}

impl<T: Transport> Builder<T> {
  /// Number of confirmations required after code deployment.
  pub fn confirmations(mut self, confirmations: usize) -> Self {
    self.confirmations = confirmations;
    self
  }

  /// Deployment transaction options.
  pub fn options(mut self, options: Options) -> Self {
    self.options = options;
    self
  }

  /// Confirmations poll interval.
  pub fn poll_interval(mut self, interval: time::Duration) -> Self {
    self.poll_interval = interval;
    self
  }

  /// Execute deployment passing code and contructor parameters.
  pub fn execute<P, V>(self, code: V, params: P, from: Address) -> Result<PendingContract<T>, ethabi::Error> where
    P: Tokenize,
    V: Into<Vec<u8>>
  {
    let options = self.options;
    let eth = self.eth;
    let abi = self.abi;

    let params = params.into_tokens();
    let data = match (abi.constructor(), params.is_empty()) {
      (None, false) => return Err(ethabi::ErrorKind::Msg(format!("Constructor is not defined in the ABI.")).into()),
      (None, true) => code.into(),
      (Some(constructor), _) => {
        constructor.encode_input(code.into(), &params)?
      },
    };

    let tx = TransactionRequest {
      from,
      to: None,
      gas: options.gas,
      gas_price: options.gas_price,
      value: options.value,
      nonce: options.nonce,
      data: Some(Bytes(data)),
      condition: options.condition,
    };

    let waiting = confirm::send_transaction_with_confirmation(
      eth.transport().clone(),
      tx,
      self.poll_interval,
      self.confirmations,
    );

    Ok(PendingContract { eth: Some(eth), abi: Some(abi), waiting, })
  }
}

/// Contract being deployed.
pub struct PendingContract<T: Transport> {
  eth: Option<Eth<T>>,
  abi: Option<ethabi::Contract>,
  waiting: confirm::SendTransactionWithConfirmation<T>,
}

impl<T: Transport> Future for PendingContract<T> {
  type Item = Contract<T>;
  type Error = Error;

  fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
    let receipt = try_ready!(self.waiting.poll());
    let eth = self.eth.take().expect("future polled after ready; qed");
    let abi = self.abi.take().expect("future polled after ready; qed");

    match receipt.contract_address {
      Some(address) => Ok(Async::Ready(Contract::new(eth, address, abi))),
      None => Err(ErrorKind::ContractDeploymentFailure(receipt.transaction_hash).into()),
    }
  }
}

#[cfg(test)]
mod tests {
  use api::{self, Namespace};
  use futures::Future;
  use helpers::tests::TestTransport;
  use rpc;
  use types::{U256};
  use contract::{Contract, Options};

  #[test]
  fn should_deploy_a_contract() {
    // given
    let mut transport = TestTransport::default();
    // Transaction Hash
    transport.add_response(rpc::Value::String("0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1".into()));
    // BlockFilter
    transport.add_response(rpc::Value::String("0x0".into()));
    // getFilterChanges
    transport.add_response(rpc::Value::Array(vec![
      rpc::Value::String("0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f".into())
    ]));
    transport.add_response(rpc::Value::Array(vec![
      rpc::Value::String("0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c548790".into())
    ]));
    // receipt
    let receipt = ::serde_json::from_str::<rpc::Value>(
        "{\"blockHash\":\"0xd5311584a9867d8e129113e1ec9db342771b94bd4533aeab820a5bcc2c54878f\",\"blockNumber\":\"0x256\",\"contractAddress\":\"0x600515dfe465f600f0c9793fa27cd2794f3ec0e1\",\"cumulativeGasUsed\":\"0xe57e0\",\"gasUsed\":\"0xe57e0\",\"logs\":[],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"root\":null,\"transactionHash\":\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\",\"transactionIndex\":\"0x0\"}"
      ).unwrap();
    transport.add_response(receipt.clone());
    // block number
    transport.add_response(rpc::Value::String("0x25a".into()));
    // receipt again
    transport.add_response(receipt);

    {
      let builder = Contract::deploy(api::Eth::new(&transport), include_bytes!("./res/token.json")).unwrap();

      // when
      builder
        .options(Options::with(|opt| opt.value = Some(5.into())))
        .confirmations(1)
        .execute(vec![1, 2, 3, 4], (U256::from(1_000_000), "My Token".to_owned(), 3u64, "MT".to_owned()), 5.into())
        .unwrap()
        .wait()
        .unwrap();
    };

    // then
    transport.assert_request("eth_sendTransaction", &[
      "{\"data\":\"0x0102030400000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000084d7920546f6b656e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d54000000000000000000000000000000000000000000000000000000000000\",\"from\":\"0x0000000000000000000000000000000000000005\",\"value\":\"0x5\"}".into(),
    ]);
    transport.assert_request("eth_newBlockFilter", &[]);
    transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
    transport.assert_request("eth_getFilterChanges", &["\"0x0\"".into()]);
    transport.assert_request("eth_getTransactionReceipt", &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()]);
    transport.assert_request("eth_blockNumber", &[]);
    transport.assert_request("eth_getTransactionReceipt", &["\"0x70ae45a5067fdf3356aa615ca08d925a38c7ff21b486a61e79d5af3969ebc1a1\"".into()]);
    transport.assert_no_more_requests();
  }
}