mod deploy;
mod event;
mod method;
use crate::{
errors::{DeployError, LinkError},
tokens::Tokenize,
};
use ethcontract_common::abi::{Error as AbiError, Result as AbiResult};
use ethcontract_common::abiext::FunctionExt;
use ethcontract_common::hash::H32;
use ethcontract_common::{Abi, Bytecode, Contract, DeploymentInformation};
use std::collections::{BTreeMap, HashMap};
use std::hash::Hash;
use web3::api::Web3;
use web3::types::{Address, Bytes, H256};
use web3::Transport;
pub use self::deploy::{Deploy, DeployBuilder};
pub use self::event::{
AllEventsBuilder, Event, EventBuilder, EventMetadata, EventStatus, ParseLog, RawLog,
StreamEvent, Topic,
};
pub use self::method::{MethodBuilder, MethodDefaults, ViewMethodBuilder};
use std::marker::PhantomData;
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct Signature<P, R>(pub H32, pub std::marker::PhantomData<(P, R)>);
impl<P, R> Signature<P, R> {
pub fn new(signature: H32) -> Self {
Signature(signature, PhantomData)
}
pub fn into_inner(self) -> H32 {
self.0
}
}
impl<P, R> From<H32> for Signature<P, R> {
fn from(signature: H32) -> Self {
Signature::new(signature)
}
}
#[derive(Debug, Clone)]
pub struct Instance<T: Transport> {
web3: Web3<T>,
abi: Abi,
address: Address,
deployment_information: Option<DeploymentInformation>,
pub defaults: MethodDefaults,
methods: HashMap<H32, (String, usize)>,
events: HashMap<H256, (String, usize)>,
}
impl<T: Transport> Instance<T> {
pub fn at(web3: Web3<T>, abi: Abi, address: Address) -> Self {
Instance::with_deployment_info(web3, abi, address, None)
}
pub fn with_deployment_info(
web3: Web3<T>,
abi: Abi,
address: Address,
deployment_information: Option<DeploymentInformation>,
) -> Self {
let methods = create_mapping(&abi.functions, |function| function.selector());
let events = create_mapping(&abi.events, |event| event.signature());
Instance {
web3,
abi,
address,
deployment_information,
defaults: MethodDefaults::default(),
methods,
events,
}
}
pub async fn deployed(web3: Web3<T>, contract: Contract) -> Result<Self, DeployError> {
let network_id = web3.net().version().await?;
let network = contract
.networks
.get(&network_id)
.ok_or(DeployError::NotFound(network_id))?;
Ok(Instance::with_deployment_info(
web3,
contract.abi,
network.address,
network.deployment_information,
))
}
pub fn builder<P>(
web3: Web3<T>,
contract: Contract,
params: P,
) -> Result<DeployBuilder<T, Self>, DeployError>
where
P: Tokenize,
{
Linker::new(contract).deploy(web3, params)
}
pub fn link_and_deploy<'a, P, I>(
web3: Web3<T>,
contract: Contract,
params: P,
libraries: I,
) -> Result<DeployBuilder<T, Self>, DeployError>
where
P: Tokenize,
I: Iterator<Item = (&'a str, Address)>,
{
let mut linker = Linker::new(contract);
for (name, address) in libraries {
linker = linker.library(name, address)?;
}
linker.deploy(web3, params)
}
pub fn web3(&self) -> Web3<T> {
self.web3.clone()
}
pub fn abi(&self) -> &Abi {
&self.abi
}
pub fn address(&self) -> Address {
self.address
}
pub fn deployment_information(&self) -> Option<DeploymentInformation> {
self.deployment_information
}
pub fn method<P, R>(
&self,
signature: impl Into<Signature<P, R>>,
params: P,
) -> AbiResult<MethodBuilder<T, R>>
where
P: Tokenize,
R: Tokenize,
{
let signature = signature.into().into_inner();
let signature = signature.as_ref();
let function = self
.methods
.get(signature)
.map(|(name, index)| &self.abi.functions[name][*index])
.ok_or_else(|| AbiError::InvalidName(hex::encode(signature)))?;
let tokens = match params.into_token() {
ethcontract_common::abi::Token::Tuple(tokens) => tokens,
_ => unreachable!("function arguments are always tuples"),
};
let data = function.encode_input(&tokens)?;
let function = function.clone();
let data = Bytes(data);
Ok(
MethodBuilder::new(self.web3(), function, self.address, data)
.with_defaults(&self.defaults),
)
}
pub fn view_method<P, R>(
&self,
signature: impl Into<Signature<P, R>>,
params: P,
) -> AbiResult<ViewMethodBuilder<T, R>>
where
P: Tokenize,
R: Tokenize,
{
Ok(self.method(signature, params)?.view())
}
pub fn fallback<D>(&self, data: D) -> AbiResult<MethodBuilder<T, ()>>
where
D: Into<Vec<u8>>,
{
if !self.abi.fallback && !self.abi.receive {
return Err(AbiError::InvalidName("fallback".into()));
}
Ok(MethodBuilder::fallback(
self.web3(),
self.address,
Bytes(data.into()),
))
}
pub fn event<E>(&self, signature: H256) -> AbiResult<EventBuilder<T, E>>
where
E: Tokenize,
{
let event = self
.events
.get(&signature)
.map(|(name, index)| &self.abi.events[name][*index])
.ok_or_else(|| AbiError::InvalidName(hex::encode(signature)))?;
Ok(EventBuilder::new(
self.web3(),
event.clone(),
self.address(),
))
}
pub fn all_events(&self) -> AllEventsBuilder<T, RawLog> {
AllEventsBuilder::new(self.web3(), self.address(), self.deployment_information())
}
}
#[derive(Debug, Clone)]
pub struct Linker {
abi: Abi,
bytecode: Bytecode,
}
impl Linker {
pub fn new(contract: Contract) -> Linker {
Linker {
abi: contract.abi,
bytecode: contract.bytecode,
}
}
pub fn library<S>(mut self, name: S, address: Address) -> Result<Linker, LinkError>
where
S: AsRef<str>,
{
self.bytecode.link(name, address)?;
Ok(self)
}
pub fn deploy<T, P>(
self,
web3: Web3<T>,
params: P,
) -> Result<DeployBuilder<T, Instance<T>>, DeployError>
where
T: Transport,
P: Tokenize,
{
DeployBuilder::new(web3, self, params)
}
}
impl<T: Transport> Deploy<T> for Instance<T> {
type Context = Linker;
fn abi(cx: &Self::Context) -> &Abi {
&cx.abi
}
fn bytecode(cx: &Self::Context) -> &Bytecode {
&cx.bytecode
}
fn from_deployment(
web3: Web3<T>,
address: Address,
transaction_hash: H256,
cx: Self::Context,
) -> Self {
Instance::with_deployment_info(
web3,
cx.abi,
address,
Some(DeploymentInformation::TransactionHash(transaction_hash)),
)
}
}
fn create_mapping<T, S, F>(
elements: &BTreeMap<String, Vec<T>>,
signature: F,
) -> HashMap<S, (String, usize)>
where
S: Hash + Eq + Ord,
F: Fn(&T) -> S,
{
let signature = &signature;
elements
.iter()
.flat_map(|(name, sub_elements)| {
sub_elements
.iter()
.enumerate()
.map(move |(index, element)| (signature(element), (name.to_owned(), index)))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::prelude::*;
use ethcontract_common::contract::Network;
use ethcontract_common::Contract;
use web3::types::H256;
#[test]
fn deployed() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let network_id = "42";
let address = addr!("0x0102030405060708091011121314151617181920");
let contract = {
let mut contract = Contract::empty();
contract.networks.insert(
network_id.to_string(),
Network {
address,
deployment_information: Some(H256::repeat_byte(0x42).into()),
},
);
contract
};
transport.add_response(json!(network_id)); let instance = Instance::deployed(web3, contract)
.immediate()
.expect("successful deployment");
transport.assert_request("net_version", &[]);
transport.assert_no_more_requests();
assert_eq!(instance.address(), address);
assert_eq!(
instance.deployment_information(),
Some(DeploymentInformation::TransactionHash(H256::repeat_byte(
0x42
)))
);
}
#[test]
fn deployed_not_found() {
let mut transport = TestTransport::new();
let web3 = Web3::new(transport.clone());
let network_id = "42";
transport.add_response(json!(network_id)); let err = Instance::deployed(web3, Contract::empty())
.immediate()
.expect_err("unexpected success getting deployed contract");
transport.assert_request("net_version", &[]);
transport.assert_no_more_requests();
assert!(
match &err {
DeployError::NotFound(id) => id == network_id,
_ => false,
},
"expected network {} not found error but got '{:?}'",
network_id,
err
);
}
}