use crate::errors::{DeployError, ExecutionError};
use crate::tokens::Tokenize;
use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
use ethcontract_common::abi::Error as AbiError;
use ethcontract_common::{Abi, Bytecode};
use std::marker::PhantomData;
use web3::api::Web3;
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 = match params.into_token() {
ethcontract_common::abi::Token::Tuple(tokens) => tokens,
_ => unreachable!("function arguments are always tuples"),
};
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 async fn deploy(self) -> Result<I, DeployError> {
let tx = match self.tx.send().await? {
TransactionResult::Receipt(tx) => tx,
TransactionResult::Hash(tx) => return Err(DeployError::Pending(tx)),
};
let transaction_hash = tx.transaction_hash;
let address = tx
.contract_address
.ok_or_else(|| ExecutionError::Failure(Box::new(tx)))?;
Ok(I::from_deployment(
self.web3,
address,
transaction_hash,
self.context,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::contract::{Instance, Linker};
use crate::test::prelude::*;
use ethcontract_common::Contract;
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 contract = Contract {
bytecode: bytecode.clone(),
..Contract::empty()
};
let linker = Linker::new(contract);
let tx = InstanceDeployBuilder::new(web3, linker, ())
.expect("error creating deploy builder")
.from(Account::Local(from, None))
.gas(1.into())
.gas_price(2.0.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.0.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 contract = Contract::empty();
let linker = Linker::new(contract);
let error = InstanceDeployBuilder::new(web3, linker, ()).err().unwrap();
assert_eq!(error.to_string(), DeployError::EmptyBytecode.to_string());
transport.assert_no_more_requests();
}
}