use crate::abicompat::AbiCompat;
use crate::errors::{DeployError, ExecutionError};
use crate::transaction::send::SendFuture;
use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
use ethcontract_common::abi::Error as AbiError;
use ethcontract_common::{Abi, Bytecode};
use futures::ready;
use pin_project::pin_project;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use web3::api::Web3;
use web3::contract::tokens::Tokenize;
use web3::types::{Address, Bytes, H256, U256};
use web3::Transport;
pub trait Deploy<T: Transport>: Sized {
type Context;
fn bytecode(cx: &Self::Context) -> &Bytecode;
fn abi(cx: &Self::Context) -> &Abi;
fn from_deployment(
web3: Web3<T>,
address: Address,
transaction_hash: H256,
cx: Self::Context,
) -> Self;
}
#[derive(Debug, Clone)]
#[must_use = "deploy builers do nothing unless you `.deploy()` them"]
pub struct DeployBuilder<T, I>
where
T: Transport,
I: Deploy<T>,
{
web3: Web3<T>,
context: I::Context,
tx: TransactionBuilder<T>,
_instance: PhantomData<I>,
}
impl<T, I> DeployBuilder<T, I>
where
T: Transport,
I: Deploy<T>,
{
pub fn new<P>(web3: Web3<T>, context: I::Context, params: P) -> Result<Self, DeployError>
where
P: Tokenize,
{
let bytecode = I::bytecode(&context);
if bytecode.is_empty() {
return Err(DeployError::EmptyBytecode);
}
let code = bytecode.to_bytes()?;
let params = params.into_tokens().compat();
let data = match (I::abi(&context).constructor(), params.is_empty()) {
(None, false) => return Err(AbiError::InvalidData.into()),
(None, true) => code,
(Some(ctor), _) => Bytes(ctor.encode_input(code.0, ¶ms)?),
};
Ok(DeployBuilder {
web3: web3.clone(),
context,
tx: TransactionBuilder::new(web3).data(data).confirmations(0),
_instance: PhantomData,
})
}
pub fn from(mut self, value: Account) -> Self {
self.tx = self.tx.from(value);
self
}
pub fn gas(mut self, value: U256) -> Self {
self.tx = self.tx.gas(value);
self
}
pub fn gas_price(mut self, value: GasPrice) -> Self {
self.tx = self.tx.gas_price(value);
self
}
pub fn value(mut self, value: U256) -> Self {
self.tx = self.tx.value(value);
self
}
pub fn nonce(mut self, value: U256) -> Self {
self.tx = self.tx.nonce(value);
self
}
pub fn confirmations(mut self, value: usize) -> Self {
self.tx = self.tx.confirmations(value);
self
}
pub fn into_inner(self) -> TransactionBuilder<T> {
self.tx
}
pub fn deploy(self) -> DeployFuture<T, I> {
DeployFuture::from_builder(self)
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[pin_project]
pub struct DeployFuture<T, I>
where
T: Transport,
I: Deploy<T>,
{
args: Option<(Web3<T>, I::Context)>,
#[pin]
send: SendFuture<T>,
_instance: PhantomData<Box<I>>,
}
impl<T, I> DeployFuture<T, I>
where
T: Transport,
I: Deploy<T>,
{
pub fn from_builder(builder: DeployBuilder<T, I>) -> Self {
DeployFuture {
args: Some((builder.web3, builder.context)),
send: builder.tx.send(),
_instance: PhantomData,
}
}
}
impl<T, I> Future for DeployFuture<T, I>
where
T: Transport,
I: Deploy<T>,
{
type Output = Result<I, DeployError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let tx = match ready!(self.as_mut().project().send.poll(cx)) {
Ok(TransactionResult::Receipt(tx)) => tx,
Ok(TransactionResult::Hash(tx)) => return Poll::Ready(Err(DeployError::Pending(tx))),
Err(err) => return Poll::Ready(Err(err.into())),
};
let address = match tx.contract_address {
Some(address) => address,
None => {
return Poll::Ready(Err(DeployError::Tx(ExecutionError::Failure(Box::new(tx)))));
}
};
let transaction_hash = tx.transaction_hash;
let (web3, context) = self.args.take().expect("called more than once");
Poll::Ready(Ok(I::from_deployment(
web3,
address,
transaction_hash,
context,
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::contract::{Instance, Linker};
use crate::test::prelude::*;
use ethcontract_common::{Artifact, Bytecode};
type InstanceDeployBuilder<T> = DeployBuilder<T, Instance<T>>;
#[test]
fn deploy_tx_options() {
let transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let from = addr!("0x9876543210987654321098765432109876543210");
let bytecode = Bytecode::from_hex_str("0x42").unwrap();
let artifact = Artifact {
bytecode: bytecode.clone(),
..Artifact::empty()
};
let linker = Linker::new(artifact);
let tx = InstanceDeployBuilder::new(web3, linker, ())
.expect("error creating deploy builder")
.from(Account::Local(from, None))
.gas(1.into())
.gas_price(2.into())
.value(28.into())
.nonce(42.into())
.into_inner();
assert_eq!(tx.from.map(|a| a.address()), Some(from));
assert_eq!(tx.to, None);
assert_eq!(tx.gas, Some(1.into()));
assert_eq!(tx.gas_price, Some(2.into()));
assert_eq!(tx.value, Some(28.into()));
assert_eq!(tx.data, Some(bytecode.to_bytes().unwrap()));
assert_eq!(tx.nonce, Some(42.into()));
transport.assert_no_more_requests();
}
#[test]
fn deploy() {
}
#[test]
fn deploy_fails_on_empty_bytecode() {
let transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let artifact = Artifact::empty();
let linker = Linker::new(artifact);
let error = InstanceDeployBuilder::new(web3, linker, ()).err().unwrap();
assert_eq!(error.to_string(), DeployError::EmptyBytecode.to_string());
transport.assert_no_more_requests();
}
}