Skip to content

Instantly share code, notes, and snippets.

@tomusdrw
Created November 28, 2017 14:10
Show Gist options
  • Select an option

  • Save tomusdrw/e8b6b3fa448a9935b35858e1aa6ceaa9 to your computer and use it in GitHub Desktop.

Select an option

Save tomusdrw/e8b6b3fa448a9935b35858e1aa6ceaa9 to your computer and use it in GitHub Desktop.
// Trait for functions will allow some nice meta programming.
type EthabiFunction {
type Output = T;
fn encoded(&self) -> ethabi::Bytes;
fn output(&self, ethabi::Bytes) -> Result<Self::Output>;
}
// A caller extension trait can be implemented for EthabiFunction in a separate crate.
impl<O, F: EthabiFunction<Output=O>> Caller for F {
fn call<C: Caller>(self, caller: C) -> C::Result {
caller.call(self.encoded(), move |bytes| self.output(bytes))
}
fn transact<T: Transact>(self, caller: T) -> T::Result {
caller.transact(self.encoded(), move |bytes| self.output(bytes))
}
}
// Trait definitions
trait Call<Out> {
// TODO do we actually need any bounds here?
type Result;
fn call<F>(self, input: ethabi::Bytes, output_decoder: F) -> Self::Result
where F: FnOnce(ethabi::Bytes) -> Result<Out, ethabi::Error>;
}
// A blanket implementations would be nice (that's the current call signature).
impl<Out, F> Call<Out> for F where
F: FnOnce(ethabi::Bytes) -> Result<ethabi::Bytes, String>
{
type Result = Result<Out, ethabi::Error>;
fn call<F>(self, input: ethabi::Bytes, output_decoder: F) -> Self::Result {
(self)(input)
.map_err(ethabi::Error::Message)
.and_then(output_decoder)
}
}
trait Transact {
type Result;
fn transact(self, ethabi::Bytes) -> Self::Result;
}
// Similar blanket impl for transact (although we don't really have bytes as result, just ())
// impl...
// Notice that futures don't need to be part of the library at all, but you can still implement asynchronous callers or transactors.
// and example of usage:
#[derive(Default)]
struct Caller;
impl<'a, Out> Call<Out> for &'a Caller {
// I can just return results.
type Result = Result<Out, String>;
fn call<F>(self, input: ethabi::Bytes, output_decoder: F) -> Self::Result
where F: FnOnce(ethabi::Bytes) -> Result<Out, ethabi::Error> {
unimplemented!()
}
}
impl<'a> Transact for &'a mut Caller {
// And here we can use futures
type Result = Box<Future<Item = (), Error = String>>;
fn transact(self, input: ethabi::Bytes) -> Self::Result {
unimplemented!()
}
}
use_contract!(eip20, "Eip20", "res/eip20.abi");
#[test]
fn example() {
// Caller decides on return types.
let caller = Caller::default();
// Initialize contract
let contract = eip20::Eip20::default();
// deploy contract (that additional function would be nice)
// (a special caller that returns address as result)
let adress = contract.constructor(a, b, c).transact(caller.deploy()).wait()?;
// Call constant function
let input_bytes = contract.functions().balance_of(a)?.encoded()?;
let balance = contract.functions().balance_of(a)?.call(&caller)?;
// In case you only need to decode output use this:
let balance = contract.outputs().balance_of(output_bytes)?;
// Transact (result dependent on the caller)
let receipt = contract.functions().transfer(b, 5)?.transact(&mut caller).wait()?;
// Read events (same patter, just passing `parse_log` as decoder)
let transfer_logs = contract.events().transfer().create_filter(c).call(&caller)?
.collect::<Vec<eip20::logs::Transfer>>();
// Alternatively since functions are traits, we can do this:
let evm = Evm::new(contract);
// Deploying a contract
evm.deploy(|contract| contract.constructor(a, b, c)).wait()?;
// Calling a function
assert_eq!(evm.call(|contract| contract.functions().balance_of(a))?, 5);
// Sending a transaction
evm.transact(|contract| contract.functions().transfer(b, 5)).wait()?;
}
@axelchalon

Copy link
Copy Markdown

wow looks very good to me! nice!

:12 got confused for a while as you impl Caller for F, but re-use the Caller name to define a struct at (:58) ^^ maybe we can call the Caller (extension trait) DelegateCall to make it more explicit/self-explanatory (resp. DelegateTransact)?
:13 I think you meant C: Call there ?
:94 I imagine you meant .encoded(); instead of .encoded()?;, as the function returns Bytes and I don't think it could fail ?

also, to make sure I understand it right, any struct implementing Call needs to keep track internally of the address contract, right?

@tomusdrw

Copy link
Copy Markdown
Author

:12 - sure, up for any name. The fact is though that I don't think it will be part of ethabi actually, perphaps part of web3-ethabi link crate for using rust-web3 or ethcore-client-ethabi crate for direct calling the Client in Parity. I think the name is up to the library to choose.
:13 yup, should be C: Call<O> to be precise.
:94 yup.

any struct implementing Call needs to keep track internally of the address contract, right?

Actually it's both true for Call and Transact, in this example Caller (struct) would most likely remember the address after caller.deploy(), it's also possible to have completely different scheme, cause this design gives us a lot of flexibility:

    /// This function handles possible errors internaly and always returns `Contract` instance.
    fn deploy() -> impl Transact<Result=Contract> {
      unimplemented!()
    }
    struct Contract { address: Address };
    // can only return u64 :)
    impl<'a> Call<u64> for &'a Contract { ... }

    let contract = eip20::Eip20::default();
    let deployed_contract = contract.constructor(a, b, c).transact(deploy());
    assert_eq!(contract.functions().balance_of(a)?.call(&deployed_contract)?, 5);

@lght

lght commented Dec 18, 2017

Copy link
Copy Markdown
// Alternatively since functions are traits, we can do this:
let evm = Evm::new(contract);
// Deploying a contract
evm.deploy(|contract| contract.constructor(a, b, c)).wait()?;

This syntax is much clearer to me, since the evm is what is responsible for deploying / interacting with contracts. IMHO, the alternative syntax obfuscates that the evm struct handles these responsibilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment