-
-
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()?; | |
| } |
: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);// 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.
wow looks very good to me! nice!
:12 got confused for a while as you impl
CallerforF, but re-use theCallername to define a struct at (:58) ^^ maybe we can call theCaller(extension trait)DelegateCallto make it more explicit/self-explanatory (resp. DelegateTransact)?:13 I think you meant
C: Callthere ?: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
Callneeds to keep track internally of the address contract, right?