Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/bin/seth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@ async fn main() -> eyre::Result<()> {
Seth::new(&rpc_url).await?.call(address, &sig, args).await?
);
}
Subcommands::SendTx {
rpc_url,
address,
sig,
args,
from,
} => {
println!(
"{}",
Seth::new(&rpc_url)
.await?
.send(
from,
address,
if sig.len() > 0 {
Some((&sig, args))
} else {
None
}
)
.await?
);
}
};

Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod opts;

mod utils;

mod seth;
pub use seth::*;

Expand Down
17 changes: 13 additions & 4 deletions src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ pub enum Subcommands {
rpc_url: String,
},
#[structopt(name = "call")]
#[structopt(
about = "Perform a local call to <to> without publishing a transaction.
"
)]
#[structopt(about = "Perform a local call to <to> without publishing a transaction.")]
Call {
#[structopt(help = "the address you want to query")]
address: Address,
Expand All @@ -44,6 +41,18 @@ pub enum Subcommands {
#[structopt(long, env = "ETH_RPC_URL")]
rpc_url: String,
},
#[structopt(name = "send")]
#[structopt(about = "Publish a transaction signed by <from> to call <to> with <data>")]
SendTx {
#[structopt(help = "the address you want to query")]
address: Address,
sig: String,
args: Vec<String>,
#[structopt(long, env = "ETH_RPC_URL")]
rpc_url: String,
#[structopt(long, env = "ETH_FROM")]
from: Address,
},
}

fn parse_block_id(s: &str) -> eyre::Result<BlockId> {
Expand Down
101 changes: 49 additions & 52 deletions src/seth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
//!
//! TODO
use ethers::{
abi::{ParamType, Tokenizable},
core::abi::parse_abi,
providers::{self, Http, Middleware, Provider},
types::*,
utils,
Expand All @@ -13,26 +11,16 @@ use rustc_hex::ToHex;
use std::convert::TryFrom;
use std::str::FromStr;

use crate::utils::get_func;

use super::utils::{encode_args, to_table};

// TODO: SethContract with common contract initializers? Same for SethProviders?

pub struct Seth {
provider: Provider<Http>,
}

fn to_table(value: serde_json::Value) -> String {
match value {
serde_json::Value::String(s) => s,
serde_json::Value::Object(map) => {
let mut s = String::new();
for (k, v) in map.iter() {
s.push_str(&format!("{: <20} {}\n", k, v));
}
s
}
_ => "".to_owned(),
}
}

impl Seth {
/// Converts ASCII text input to hex
///
Expand All @@ -49,8 +37,10 @@ impl Seth {
Ok(Self { provider })
}

// TODO: `send`, same story but sending the tx.
/// Makes a read-only call to the specified address
///
/// ```no_run
///
/// use dapptools::Seth;
/// use dapptools::ethers::types::Address;
/// use std::str::FromStr;
Expand All @@ -66,38 +56,8 @@ impl Seth {
/// # }
/// ```
pub async fn call(&self, to: Address, sig: &str, args: Vec<String>) -> Result<String> {
// TODO: Make human readable ABI better / more minimal
let abi = parse_abi(&[sig])?;
// get the function
let func = {
let (_, func) = abi
.functions
.iter()
.next()
.ok_or_else(|| eyre::eyre!("function name not found"))?;
let func = func
.get(0)
.ok_or_else(|| eyre::eyre!("functions array empty"))?;
if args.len() != func.inputs.len() {
eyre::bail!("function inputs do len does not match provided args len");
}
func
};

// Dynamically build up the calldata via the function sig
let mut inputs = Vec::new();
for (i, input) in func.inputs.iter().enumerate() {
let input = match input.kind {
// TODO: Do the rest of the types
ParamType::Address => Address::from_str(&args[i])?.into_token(),
ParamType::Uint(256) => U256::from_str(&args[i])?.into_token(),
_ => Address::zero().into_token(),
};
inputs.push(input);
}

// encode args
let data = func.encode_input(&inputs)?;
let func = get_func(&sig)?;
let data = encode_args(&func, args)?;

// make the call
let tx = Eip1559TransactionRequest::new().to(to).data(data).into();
Expand All @@ -107,13 +67,50 @@ impl Seth {
let res = func.decode_output(res.as_ref())?;

// concatenate them
let mut s = Vec::new();
let mut s = String::new();
for output in res {
s.push(format!("{}", output));
s.push_str(&format!("{}\n", output));
}

// return string
Ok(s.join(","))
Ok(s)
}

/// Sends a transaction to the specified address
///
/// ```no_run
/// use dapptools::Seth;
/// use dapptools::ethers::types::Address;
/// use std::str::FromStr;
///
/// # async fn foo() -> eyre::Result<()> {
/// let seth = Seth::new("http://localhost:8545").await?;
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let sig = "function greetg(string memory) public returns (string)";
/// let args = vec!["5".to_owned()];
/// let data = seth.call(to, sig, args).await?;
/// println!("{}", data);
/// # Ok(())
/// # }
/// ```
pub async fn send(
&self,
from: Address,
to: Address,
args: Option<(&str, Vec<String>)>,
) -> Result<String> {
// make the call
let mut tx = Eip1559TransactionRequest::new().from(from).to(to);

if let Some((sig, args)) = args {
let func = get_func(&sig)?;
let data = encode_args(&func, args)?;
tx = tx.data(data);
}

let res = self.provider.send_transaction(tx, None).await?;

Ok(format!("{:?}", *res))
}

/// ```no_run
Expand Down
102 changes: 102 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use ethers::{
abi::{Function, ParamType, Token, Tokenizable},
core::abi::parse_abi,
types::*,
};
use eyre::Result;
use rustc_hex::FromHex;
use std::str::FromStr;

// TODO: SethContract with common contract initializers? Same for SethProviders?
pub fn to_table(value: serde_json::Value) -> String {
match value {
serde_json::Value::String(s) => s,
serde_json::Value::Object(map) => {
let mut s = String::new();
for (k, v) in map.iter() {
s.push_str(&format!("{: <20} {}\n", k, v));
}
s
}
_ => "".to_owned(),
}
}

pub fn get_func(sig: &str) -> Result<Function> {
// TODO: Make human readable ABI better / more minimal
let abi = parse_abi(&[sig])?;
// get the function
let (_, func) = abi
.functions
.iter()
.next()
.ok_or_else(|| eyre::eyre!("function name not found"))?;
let func = func
.get(0)
.ok_or_else(|| eyre::eyre!("functions array empty"))?;
Ok(func.clone())
}

pub fn encode_input(param: &ParamType, value: &str) -> Result<Token> {
Ok(match param {
// TODO: Do the rest of the types
ParamType::Address => Address::from_str(&value)?.into_token(),
ParamType::Bytes => Bytes::from(value.from_hex::<Vec<u8>>()?).into_token(),
ParamType::FixedBytes(_) => value.from_hex::<Vec<u8>>()?.into_token(),
ParamType::Uint(n) => {
let radix = if value.starts_with("0x") { 16 } else { 10 };
match n / 8 {
1 => u8::from_str_radix(value, radix)?.into_token(),
2 => u16::from_str_radix(value, radix)?.into_token(),
3..=4 => u32::from_str_radix(value, radix)?.into_token(),
5..=8 => u64::from_str_radix(value, radix)?.into_token(),
9..=16 => u128::from_str_radix(value, radix)?.into_token(),
17..=32 => if radix == 16 {
U256::from_str(value)?
} else {
U256::from_dec_str(value)?
}
.into_token(),
_ => eyre::bail!("unsupoprted solidity type uint{}", n),
}
}
ParamType::Int(n) => {
let radix = if value.starts_with("0x") { 16 } else { 10 };
match n / 8 {
1 => i8::from_str_radix(value, radix)?.into_token(),
2 => i16::from_str_radix(value, radix)?.into_token(),
3..=4 => i32::from_str_radix(value, radix)?.into_token(),
5..=8 => i64::from_str_radix(value, radix)?.into_token(),
9..=16 => i128::from_str_radix(value, radix)?.into_token(),
17..=32 => if radix == 16 {
I256::from_str(value)?
} else {
I256::from_dec_str(value)?
}
.into_token(),
_ => eyre::bail!("unsupoprted solidity type uint{}", n),
}
}
ParamType::Bool => bool::from_str(value)?.into_token(),
ParamType::String => value.to_string().into_token(),
ParamType::Array(_) => {
unimplemented!()
}
ParamType::FixedArray(_, _) => {
unimplemented!()
}
ParamType::Tuple(_) => {
unimplemented!()
}
})
}

pub fn encode_args(func: &Function, args: Vec<String>) -> Result<Vec<u8>> {
// Dynamically build up the calldata via the function sig
let mut inputs = Vec::new();
for (i, input) in func.inputs.iter().enumerate() {
let input = encode_input(&input.kind, &args[i])?;
inputs.push(input);
}
Ok(func.encode_input(&inputs)?)
}