feat: add basic contract support
This commit is contained in:
parent
1dd3c3dc89
commit
33b36bbc52
|
@ -66,6 +66,16 @@ version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -235,6 +245,7 @@ name = "ethers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"bincode",
|
||||||
"ethabi",
|
"ethabi",
|
||||||
"ethereum-types",
|
"ethereum-types",
|
||||||
"failure",
|
"failure",
|
||||||
|
|
|
@ -21,6 +21,7 @@ tiny-keccak = { version = "2.0.2", default-features = false }
|
||||||
solc = { git = "https://github.com/paritytech/rust_solc "}
|
solc = { git = "https://github.com/paritytech/rust_solc "}
|
||||||
rlp = "0.4.5"
|
rlp = "0.4.5"
|
||||||
ethabi = "12.0.0"
|
ethabi = "12.0.0"
|
||||||
|
bincode = "1.2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["macros"] }
|
tokio = { version = "0.2.21", features = ["macros"] }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use ethers::{
|
use ethers::{
|
||||||
|
abi::ParamType,
|
||||||
|
contract::Contract,
|
||||||
types::{Address, Filter},
|
types::{Address, Filter},
|
||||||
Contract, HttpProvider, MainnetWallet,
|
HttpProvider, MainnetWallet,
|
||||||
};
|
};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
@ -10,16 +12,59 @@ async fn main() -> Result<(), failure::Error> {
|
||||||
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
||||||
|
|
||||||
// create a wallet and connect it to the provider
|
// create a wallet and connect it to the provider
|
||||||
let client = "15c42bf2987d5a8a73804a8ea72fb4149f88adf73e98fc3f8a8ce9f24fcb7774"
|
let client = "d22cf25d564c3c3f99677f8710b2f045045f16eccd31140c92d6feb18c1169e9"
|
||||||
.parse::<MainnetWallet>()?
|
.parse::<MainnetWallet>()?
|
||||||
.connect(&provider);
|
.connect(&provider);
|
||||||
|
|
||||||
// Contract should take both provider or a signer
|
// Contract should take both provider or a signer
|
||||||
|
|
||||||
let contract = Contract::new(
|
// get the contract's address
|
||||||
"f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse::<Address>()?,
|
let addr = "683BEE23D79A1D8664dF70714edA966e1484Fd3d".parse::<Address>()?;
|
||||||
abi,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// get the contract's ABI
|
||||||
|
let abi = r#"[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#;
|
||||||
|
|
||||||
|
// instantiate it
|
||||||
|
let contract = Contract::new(&client, serde_json::from_str(abi)?, addr);
|
||||||
|
|
||||||
|
// get the args
|
||||||
|
let event = "ValueChanged(address,string,string)";
|
||||||
|
|
||||||
|
let args = &[ethabi::Token::String("hello!".to_owned())];
|
||||||
|
|
||||||
|
// call the method
|
||||||
|
let tx_hash = contract.method("setValue", args)?.send().await?;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ValueChanged {
|
||||||
|
author: Address,
|
||||||
|
old_value: String,
|
||||||
|
new_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let filter = Filter::new().from_block(0).address(addr).event(event);
|
||||||
|
let logs = provider
|
||||||
|
.get_logs(&filter)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|log| {
|
||||||
|
// decode the non-indexed data
|
||||||
|
let data = ethabi::decode(&[ParamType::String, ParamType::String], log.data.as_ref())?;
|
||||||
|
|
||||||
|
let author = log.topics[1].into();
|
||||||
|
|
||||||
|
// Unwrap?
|
||||||
|
let old_value = data[0].clone().to_string().unwrap();
|
||||||
|
let new_value = data[1].clone().to_string().unwrap();
|
||||||
|
|
||||||
|
Ok(ValueChanged {
|
||||||
|
old_value,
|
||||||
|
new_value,
|
||||||
|
author,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, ethabi::Error>>()?;
|
||||||
|
|
||||||
|
dbg!(logs);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ async fn main() -> Result<(), failure::Error> {
|
||||||
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
let provider = HttpProvider::try_from("http://localhost:8545")?;
|
||||||
|
|
||||||
// create a wallet and connect it to the provider
|
// create a wallet and connect it to the provider
|
||||||
let client = "15c42bf2987d5a8a73804a8ea72fb4149f88adf73e98fc3f8a8ce9f24fcb7774"
|
let client = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"
|
||||||
.parse::<MainnetWallet>()?
|
.parse::<MainnetWallet>()?
|
||||||
.connect(&provider);
|
.connect(&provider);
|
||||||
|
|
||||||
|
@ -17,10 +17,10 @@ async fn main() -> Result<(), failure::Error> {
|
||||||
.value(10000);
|
.value(10000);
|
||||||
|
|
||||||
// send it!
|
// send it!
|
||||||
let tx = client.sign_and_send_transaction(tx, None).await?;
|
let hash = client.send_transaction(tx, None).await?;
|
||||||
|
|
||||||
// get the mined tx
|
// get the mined tx
|
||||||
let tx = client.get_transaction(tx.hash).await?;
|
let tx = client.get_transaction(hash).await?;
|
||||||
|
|
||||||
let receipt = client.get_transaction_receipt(tx.hash).await?;
|
let receipt = client.get_transaction_receipt(tx.hash).await?;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
//! This module implements extensions to the `ethabi` API.
|
//! This module implements extensions to the `ethabi` API.
|
||||||
//! Taken from: https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs
|
//! Taken from: https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs
|
||||||
|
|
||||||
use ethabi::{Event, Function, ParamType};
|
pub use ethabi::Contract as Abi;
|
||||||
use crate::{utils::id, types::Selector};
|
pub use ethabi::*;
|
||||||
|
|
||||||
|
use crate::{types::Selector, utils::id};
|
||||||
|
|
||||||
/// Extension trait for `ethabi::Function`.
|
/// Extension trait for `ethabi::Function`.
|
||||||
pub trait FunctionExt {
|
pub trait FunctionExt {
|
|
@ -0,0 +1,169 @@
|
||||||
|
use crate::{
|
||||||
|
abi::{Abi, Function, FunctionExt},
|
||||||
|
providers::JsonRpcClient,
|
||||||
|
signers::{Client, Signer},
|
||||||
|
types::{Address, BlockNumber, Selector, TransactionRequest, H256, U256},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustc_hex::ToHex;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
|
/// Represents a contract instance at an address. Provides methods for
|
||||||
|
/// contract interaction.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Contract<'a, S, P> {
|
||||||
|
client: &'a Client<'a, S, P>,
|
||||||
|
abi: Abi,
|
||||||
|
address: Address,
|
||||||
|
|
||||||
|
/// A mapping from method signature to a name-index pair for accessing
|
||||||
|
/// functions in the contract ABI. This is used to avoid allocation when
|
||||||
|
/// searching for matching functions by signature.
|
||||||
|
methods: HashMap<Selector, (String, usize)>,
|
||||||
|
|
||||||
|
/// A mapping from event signature to a name-index pair for resolving
|
||||||
|
/// events in the contract ABI.
|
||||||
|
events: HashMap<H256, (String, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, P> Contract<'a, S, P> {
|
||||||
|
/// Creates a new contract from the provided client, abi and address
|
||||||
|
pub fn new(client: &'a Client<'a, S, P>, abi: Abi, address: Address) -> Self {
|
||||||
|
let methods = create_mapping(&abi.functions, |function| function.selector());
|
||||||
|
let events = create_mapping(&abi.events, |event| event.signature());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
client,
|
||||||
|
abi,
|
||||||
|
address,
|
||||||
|
methods,
|
||||||
|
events,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a transaction builder for the provided function name. If there are
|
||||||
|
/// multiple functions with the same name due to overloading, consider using
|
||||||
|
/// the `method_hash` method instead, since this will use the first match.
|
||||||
|
pub fn method(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &[ethabi::Token],
|
||||||
|
) -> Result<Sender<'a, S, P>, ethabi::Error> {
|
||||||
|
// get the function
|
||||||
|
let function = self.abi.function(name)?;
|
||||||
|
self.method_func(function, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a transaction builder for the selected function signature. This should be
|
||||||
|
/// preferred if there are overloaded functions in your smart contract
|
||||||
|
pub fn method_hash(
|
||||||
|
&self,
|
||||||
|
signature: Selector,
|
||||||
|
args: &[ethabi::Token],
|
||||||
|
) -> Result<Sender<'a, S, P>, ethabi::Error> {
|
||||||
|
let function = self
|
||||||
|
.methods
|
||||||
|
.get(&signature)
|
||||||
|
.map(|(name, index)| &self.abi.functions[name][*index])
|
||||||
|
.ok_or_else(|| ethabi::Error::InvalidName(signature.to_hex::<String>()))?;
|
||||||
|
self.method_func(function, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method_func(
|
||||||
|
&self,
|
||||||
|
function: &Function,
|
||||||
|
args: &[ethabi::Token],
|
||||||
|
) -> Result<Sender<'a, S, P>, ethabi::Error> {
|
||||||
|
// create the calldata
|
||||||
|
let data = function.encode_input(args)?;
|
||||||
|
|
||||||
|
// create the tx object
|
||||||
|
let tx = TransactionRequest {
|
||||||
|
to: Some(self.address),
|
||||||
|
data: Some(data.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Sender {
|
||||||
|
tx,
|
||||||
|
client: self.client,
|
||||||
|
block: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn address(&self) -> &Address {
|
||||||
|
&self.address
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abi(&self) -> &Abi {
|
||||||
|
&self.abi
|
||||||
|
}
|
||||||
|
|
||||||
|
// call events
|
||||||
|
// deploy
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sender<'a, S, P> {
|
||||||
|
tx: TransactionRequest,
|
||||||
|
client: &'a Client<'a, S, P>,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, P> Sender<'a, S, P> {
|
||||||
|
/// Sets the `from` field in the transaction to the provided value
|
||||||
|
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
|
||||||
|
self.tx.from = Some(from.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `gas` field in the transaction to the provided value
|
||||||
|
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
|
||||||
|
self.tx.gas = Some(gas.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `gas_price` field in the transaction to the provided value
|
||||||
|
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
|
||||||
|
self.tx.gas_price = Some(gas_price.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `value` field in the transaction to the provided value
|
||||||
|
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
|
||||||
|
self.tx.value = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: Signer, P: JsonRpcClient> Sender<'a, S, P> {
|
||||||
|
pub async fn call<T: for<'b> Deserialize<'b>>(self) -> Result<T, P::Error> {
|
||||||
|
self.client.call(self.tx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(self) -> Result<H256, P::Error> {
|
||||||
|
self.client.send_transaction(self.tx, self.block).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function for creating a mapping between a unique signature and a
|
||||||
|
/// name-index pair for accessing contract ABI items.
|
||||||
|
fn create_mapping<T, S, F>(
|
||||||
|
elements: &HashMap<String, Vec<T>>,
|
||||||
|
signature: F,
|
||||||
|
) -> HashMap<S, (String, usize)>
|
||||||
|
where
|
||||||
|
S: Hash + Eq,
|
||||||
|
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()
|
||||||
|
}
|
|
@ -1,17 +1,2 @@
|
||||||
use crate::types::Address;
|
mod contract;
|
||||||
|
pub use contract::Contract;
|
||||||
mod abi;
|
|
||||||
|
|
||||||
pub struct Contract<ABI> {
|
|
||||||
pub address: Address,
|
|
||||||
pub abi: ABI,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<ABI> Contract<ABI> {
|
|
||||||
pub fn new<A: Into<Address>>(address: A, abi: ABI) -> Self {
|
|
||||||
Self {
|
|
||||||
address: address.into(),
|
|
||||||
abi,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
pub use providers::HttpProvider;
|
pub use providers::HttpProvider;
|
||||||
|
|
||||||
mod contract;
|
pub mod contract;
|
||||||
pub use contract::Contract;
|
pub use contract::Contract;
|
||||||
|
|
||||||
mod signers;
|
pub(crate) mod signers;
|
||||||
pub use signers::{AnyWallet, MainnetWallet, Signer};
|
pub use signers::{AnyWallet, MainnetWallet, Signer};
|
||||||
|
|
||||||
/// Ethereum related datatypes
|
/// Ethereum related datatypes
|
||||||
|
@ -32,3 +32,6 @@ pub use solc;
|
||||||
|
|
||||||
/// Various utilities
|
/// Various utilities
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
/// ABI utilities
|
||||||
|
pub mod abi;
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
/// Connects to a signer and returns a client
|
/// Connects to a signer and returns a client
|
||||||
pub fn connect<S: Signer>(&self, signer: S) -> Client<S, P> {
|
pub fn connect<S: Signer>(&self, signer: S) -> Client<S, P> {
|
||||||
Client {
|
Client {
|
||||||
signer,
|
signer: Some(signer),
|
||||||
provider: self,
|
provider: self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,14 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
|
|
||||||
// State mutations
|
// State mutations
|
||||||
|
|
||||||
|
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
||||||
|
pub async fn call<T: for<'a> Deserialize<'a>>(
|
||||||
|
&self,
|
||||||
|
tx: TransactionRequest,
|
||||||
|
) -> Result<T, P::Error> {
|
||||||
|
self.0.request("eth_call", Some(tx)).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
||||||
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
||||||
self.0.request("eth_sendTransaction", Some(tx)).await
|
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
providers::{JsonRpcClient, Provider},
|
providers::{JsonRpcClient, Provider},
|
||||||
signers::Signer,
|
signers::Signer,
|
||||||
types::{Address, BlockNumber, Transaction, TransactionRequest},
|
types::{Address, BlockNumber, Overrides, TransactionRequest, TxHash},
|
||||||
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -9,18 +10,52 @@ use std::ops::Deref;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Client<'a, S, P> {
|
pub struct Client<'a, S, P> {
|
||||||
pub(crate) provider: &'a Provider<P>,
|
pub(crate) provider: &'a Provider<P>,
|
||||||
pub(crate) signer: S,
|
pub(crate) signer: Option<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, P> From<&'a Provider<P>> for Client<'a, S, P> {
|
||||||
|
fn from(provider: &'a Provider<P>) -> Self {
|
||||||
|
Client {
|
||||||
|
provider,
|
||||||
|
signer: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> {
|
impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> {
|
||||||
/// Signs the transaction and then broadcasts its RLP encoding via the `eth_sendRawTransaction`
|
/// Signs the transaction and then broadcasts its RLP encoding via the `eth_sendRawTransaction`
|
||||||
/// API
|
/// API
|
||||||
pub async fn sign_and_send_transaction(
|
pub async fn send_transaction(
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<Transaction, P::Error> {
|
) -> Result<TxHash, P::Error> {
|
||||||
|
// if there is no local signer, then the transaction should use the
|
||||||
|
// node's signer which should already be unlocked
|
||||||
|
let signer = if let Some(ref signer) = self.signer {
|
||||||
|
signer
|
||||||
|
} else {
|
||||||
|
return self.provider.send_transaction(tx).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fill any missing fields
|
||||||
|
self.fill_transaction(&mut tx, block).await?;
|
||||||
|
|
||||||
|
// sign the transaction
|
||||||
|
let signed_tx = signer.sign_transaction(tx).unwrap(); // TODO
|
||||||
|
|
||||||
|
// broadcast it
|
||||||
|
self.provider.send_raw_transaction(&signed_tx).await?;
|
||||||
|
|
||||||
|
Ok(signed_tx.hash)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Convert to join'ed futures
|
// TODO: Convert to join'ed futures
|
||||||
|
async fn fill_transaction(
|
||||||
|
&self,
|
||||||
|
tx: &mut TransactionRequest,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
) -> Result<(), P::Error> {
|
||||||
// get the gas price
|
// get the gas price
|
||||||
if tx.gas_price.is_none() {
|
if tx.gas_price.is_none() {
|
||||||
tx.gas_price = Some(self.provider.get_gas_price().await?);
|
tx.gas_price = Some(self.provider.get_gas_price().await?);
|
||||||
|
@ -41,17 +76,48 @@ impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign the transaction
|
Ok(())
|
||||||
let signed_tx = self.signer.sign_transaction(tx).unwrap(); // TODO
|
}
|
||||||
|
|
||||||
// broadcast it
|
/// client.call_contract(
|
||||||
self.provider.send_raw_transaction(&signed_tx).await?;
|
/// addr,
|
||||||
|
/// "transfer(address,uint256)"
|
||||||
|
/// vec![0x1234, 100]
|
||||||
|
/// None,
|
||||||
|
/// None,
|
||||||
|
/// )
|
||||||
|
pub async fn call_contract(
|
||||||
|
&self,
|
||||||
|
to: impl Into<Address>,
|
||||||
|
signature: &str,
|
||||||
|
args: &[ethabi::Token],
|
||||||
|
overrides: Option<Overrides>,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
) -> Result<TxHash, P::Error> {
|
||||||
|
// create the data field from the function signature and the arguments
|
||||||
|
let data = [&utils::id(signature)[..], ðabi::encode(args)].concat();
|
||||||
|
|
||||||
Ok(signed_tx)
|
let overrides = overrides.unwrap_or_default();
|
||||||
|
let tx = TransactionRequest {
|
||||||
|
to: Some(to.into()),
|
||||||
|
data: Some(data.into()),
|
||||||
|
|
||||||
|
// forward the overriden data
|
||||||
|
from: overrides.from, // let it figure it out itself
|
||||||
|
gas: overrides.gas,
|
||||||
|
gas_price: overrides.gas_price,
|
||||||
|
nonce: overrides.nonce,
|
||||||
|
value: overrides.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_transaction(tx, block).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> Address {
|
pub fn address(&self) -> Address {
|
||||||
self.signer.address()
|
self.signer
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.address())
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ impl<N: Network> Wallet<N> {
|
||||||
/// Connects to a provider and returns a client
|
/// Connects to a provider and returns a client
|
||||||
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P>) -> Client<Wallet<N>, P> {
|
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P>) -> Client<Wallet<N>, P> {
|
||||||
Client {
|
Client {
|
||||||
signer: self,
|
signer: Some(self),
|
||||||
provider,
|
provider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,12 @@ pub struct Bytes(
|
||||||
pub Vec<u8>,
|
pub Vec<u8>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Bytes {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Bytes {
|
impl Bytes {
|
||||||
/// Returns an empty bytes vector
|
/// Returns an empty bytes vector
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub use ethereum_types::H256 as TxHash;
|
||||||
pub use ethereum_types::{Address, Bloom, H256, U256, U64};
|
pub use ethereum_types::{Address, Bloom, H256, U256, U64};
|
||||||
|
|
||||||
mod transaction;
|
mod transaction;
|
||||||
pub use transaction::{Transaction, TransactionReceipt, TransactionRequest};
|
pub use transaction::{Overrides, Transaction, TransactionReceipt, TransactionRequest};
|
||||||
|
|
||||||
mod keys;
|
mod keys;
|
||||||
pub use keys::{PrivateKey, PublicKey, TxError};
|
pub use keys::{PrivateKey, PublicKey, TxError};
|
||||||
|
@ -23,5 +23,3 @@ pub use block::{Block, BlockId, BlockNumber};
|
||||||
|
|
||||||
mod log;
|
mod log;
|
||||||
pub use log::{Filter, Log};
|
pub use log::{Filter, Log};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,31 @@ use rlp::RlpStream;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// Override params for interacting with a contract
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Overrides {
|
||||||
|
/// Sender address or ENS name
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) from: Option<Address>,
|
||||||
|
|
||||||
|
/// Supplied gas (None for sensible default)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) gas: Option<U256>,
|
||||||
|
|
||||||
|
/// Gas price (None for sensible default)
|
||||||
|
#[serde(rename = "gasPrice")]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) gas_price: Option<U256>,
|
||||||
|
|
||||||
|
/// Transfered value (None for no transfer)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) value: Option<U256>,
|
||||||
|
|
||||||
|
/// Transaction nonce (None for next available nonce)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) nonce: Option<U256>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Parameters for sending a transaction
|
/// Parameters for sending a transaction
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
pub struct TransactionRequest {
|
pub struct TransactionRequest {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! Various utilities for manipulating Ethereum related dat
|
//! Various utilities for manipulating Ethereum related dat
|
||||||
use crate::types::{H256, Selector};
|
use crate::types::{Selector, H256};
|
||||||
use tiny_keccak::{Hasher, Keccak};
|
use tiny_keccak::{Hasher, Keccak};
|
||||||
|
|
||||||
const PREFIX: &str = "\x19Ethereum Signed Message:\n";
|
const PREFIX: &str = "\x19Ethereum Signed Message:\n";
|
||||||
|
|
Loading…
Reference in New Issue