// Copyright 2015-2017 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . //! Rust code contract generator. //! The code generated will require a dependence on the `ethcore-ethereum_types`, //! `ethabi`, `byteorder`, and `futures` crates. //! This currently isn't hygienic, so compilation of generated code may fail //! due to missing crates or name collisions. This will change when //! it can be ported to a procedural macro. extern crate ethabi; extern crate heck; use ethabi::{Contract, ParamType}; use heck::SnakeCase; /// Errors in generation. #[derive(Debug)] pub enum Error { /// Bad ABI. Abi(ethabi::Error), /// Unsupported parameter type in given function. UnsupportedType(String, ParamType), } /// Given an ABI string, generate code for a a Rust module containing /// a struct which can be used to call it. // TODO: make this a proc macro when that's possible. pub fn generate_module(struct_name: &str, abi: &str) -> Result { let contract = Contract::load(abi.as_bytes()).map_err(Error::Abi)?; let functions = generate_functions(&contract)?; Ok(format!(r##" use byteorder::{{BigEndian, ByteOrder}}; use futures::{{future, Future, IntoFuture}}; use ethabi::{{Bytes, Contract, Token, Event}}; use ethereum_types; type BoxFuture = Box + Send>; /// Generated Rust bindings to an Ethereum contract. #[derive(Clone, Debug)] pub struct {name} {{ contract: Contract, /// Address to make calls to. pub address: ethereum_types::H160, }} const ABI: &'static str = r#"{abi_str}"#; impl {name} {{ /// Create a new instance of `{name}` with an address. /// Calls can be made, given a callback for dispatching calls asynchronously. pub fn new(address: ethereum_types::H160) -> Self {{ let contract = Contract::load(ABI.as_bytes()) .expect("ABI checked at generation-time; qed"); {name} {{ contract: contract, address: address, }} }} /// Access the underlying `ethabi` contract. pub fn contract(this: &Self) -> &Contract {{ &this.contract }} {functions} }} "##, name = struct_name, abi_str = abi, functions = functions, )) } // generate function bodies from the ABI. fn generate_functions(contract: &Contract) -> Result { let mut functions = String::new(); for function in contract.functions() { let name = &function.name; let snake_name = name.to_snake_case(); let inputs: Vec<_> = function.inputs.iter().map(|i| i.kind.clone()).collect(); let outputs: Vec<_> = function.outputs.iter().map(|i| i.kind.clone()).collect(); let (input_params, input_names, to_tokens) = input_params_codegen(&inputs) .map_err(|bad_type| Error::UnsupportedType(name.clone(), bad_type))?; let (output_type, decode_outputs) = output_params_codegen(&outputs) .map_err(|bad_type| Error::UnsupportedType(name.clone(), bad_type))?; functions.push_str(&format!(r##" /// Call the function "{abi_name}" on the contract. /// /// Inputs: {abi_inputs:?} /// Outputs: {abi_outputs:?} pub fn {snake_name}(&self, call: F, {params}) -> BoxFuture<{output_type}, String> where F: FnOnce(ethereum_types::H160, Vec) -> U, U: IntoFuture, Error=String>, U::Future: Send + 'static {{ let call_addr = self.address; let call_future = match self.encode_{snake_name}_input({params_names}) {{ Ok(call_data) => (call)(call_addr, call_data), Err(e) => return Box::new(future::err(e)), }}; let function = self.contract.function(r#"{abi_name}"#) .expect("function existence checked at compile-time; qed").clone(); Box::new(call_future .into_future() .and_then(move |out| function.decode_output(&out).map_err(|e| format!("{{:?}}", e))) .map(Vec::into_iter) .and_then(|mut outputs| {decode_outputs})) }} /// Encode "{abi_name}" function arguments. /// Arguments: {abi_inputs:?} pub fn encode_{snake_name}_input(&self, {params}) -> Result, String> {{ self.contract.function(r#"{abi_name}"#) .expect("function existence checked at compile-time; qed") .encode_input(&{to_tokens}) .map_err(|e| format!("Error encoding call: {{:?}}", e)) }} "##, abi_name = name, abi_inputs = inputs, abi_outputs = outputs, snake_name = snake_name, params = input_params, params_names = input_names, output_type = output_type, to_tokens = to_tokens, decode_outputs = decode_outputs, )) } Ok(functions) } // generate code for params in function signature and turning them into tokens. // // three pieces of code are generated: the first gives input types for the function signature, // the second one gives input parameter names to pass to another method, // and the third gives code to tokenize those inputs. // // params of form `param_0: type_0, param_1: type_1, ...` // tokenizing code of form `{let mut tokens = Vec::new(); tokens.push({param_X}); tokens }` // // returns any unsupported param type encountered. fn input_params_codegen(inputs: &[ParamType]) -> Result<(String, String, String), ParamType> { let mut params = String::new(); let mut params_names = String::new(); let mut to_tokens = "{ let mut tokens = Vec::new();".to_string(); for (index, param_type) in inputs.iter().enumerate() { let param_name = format!("param_{}", index); let rust_type = rust_type(param_type.clone())?; let (needs_mut, tokenize_code) = tokenize(¶m_name, param_type.clone()); params.push_str(&format!("{}{}: {}, ", if needs_mut { "mut " } else { "" }, param_name, rust_type)); params_names.push_str(&format!("{}, ", param_name)); to_tokens.push_str(&format!("tokens.push({{ {} }});", tokenize_code)); } to_tokens.push_str(" tokens }"); Ok((params, params_names, to_tokens)) } // generate code for outputs of the function and detokenizing them. // // two pieces of code are generated: the first gives an output type for the function signature // as a tuple, and the second gives code to get that tuple from a deque of tokens. // // produce output type of the form (type_1, type_2, ...) without trailing comma. // produce code for getting this output type from `outputs: Vec::IntoIter`, where // an `Err(String)` can be returned. // // returns any unsupported param type encountered. fn output_params_codegen(outputs: &[ParamType]) -> Result<(String, String), ParamType> { let mut output_type = "(".to_string(); let mut decode_outputs = "Ok((".to_string(); for (index, output) in outputs.iter().cloned().enumerate() { let rust_type = rust_type(output.clone())?; output_type.push_str(&rust_type); decode_outputs.push_str(&format!( r#" outputs .next() .and_then(|output| {{ {} }}) .ok_or_else(|| "Wrong output type".to_string())? "#, detokenize("output", output) )); // don't append trailing commas for the last element // so we can reuse the same code for single-output contracts, // since T == (T) != (T,) if index < outputs.len() - 1 { output_type.push_str(", "); decode_outputs.push_str(", "); } } output_type.push_str(")"); decode_outputs.push_str("))"); Ok((output_type, decode_outputs)) } // create code for an argument type from param type. fn rust_type(input: ParamType) -> Result { Ok(match input { ParamType::Address => "ethereum_types::H160".into(), ParamType::FixedBytes(len) if len <= 32 => format!("ethereum_types::H{}", len * 8), ParamType::Bytes | ParamType::FixedBytes(_) => "Vec".into(), ParamType::Int(width) => match width { 8 | 16 | 32 | 64 => format!("i{}", width), _ => return Err(ParamType::Int(width)), }, ParamType::Uint(width) => match width { 8 | 16 | 32 | 64 => format!("u{}", width), 128 | 160 | 256 => format!("ethereum_types::U{}", width), _ => return Err(ParamType::Uint(width)), }, ParamType::Bool => "bool".into(), ParamType::String => "String".into(), ParamType::Array(kind) => format!("Vec<{}>", rust_type(*kind)?), other => return Err(other), }) } // create code for tokenizing this parameter. // returns (needs_mut, code), where needs_mut indicates mutability required. // panics on unsupported types. fn tokenize(name: &str, input: ParamType) -> (bool, String) { let mut needs_mut = false; let code = match input { ParamType::Address => format!("Token::Address({}.0)", name), ParamType::Bytes => format!("Token::Bytes({})", name), ParamType::FixedBytes(len) if len <= 32 => format!("Token::FixedBytes({}.0.to_vec())", name), ParamType::FixedBytes(len) => { needs_mut = true; format!("{}.resize({}, 0); Token::FixedBytes({})", name, len, name) } ParamType::Int(width) => match width { 8 => format!("let mut r = [0xff; 32]; r[31] = {}; Token::Int(r)", name), 16 | 32 | 64 => format!("let mut r = [0xff; 32]; BigEndian::write_i{}(&mut r[{}..], {}); Token::Int(r))", width, 32 - (width / 8), name), _ => panic!("Signed int with more than 64 bits not supported."), }, ParamType::Uint(width) => format!( "let mut r = [0; 32]; {}.to_big_endian(&mut r); Token::Uint(r)", if width <= 64 { format!("ethereum_types::U256::from({} as u64)", name) } else { format!("ethereum_types::U256::from({})", name) } ), ParamType::Bool => format!("Token::Bool({})", name), ParamType::String => format!("Token::String({})", name), ParamType::Array(kind) => { let (needs_mut, code) = tokenize("i", *kind); format!("Token::Array({}.into_iter().map(|{}i| {{ {} }}).collect())", name, if needs_mut { "mut " } else { "" }, code) } ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported."), }; (needs_mut, code) } // create code for detokenizing this parameter. // takes an output type and the identifier of a token. // expands to code that evaluates to a Option // panics on unsupported types. fn detokenize(name: &str, output_type: ParamType) -> String { match output_type { ParamType::Address => format!("{}.to_address().map(ethereum_types::H160)", name), ParamType::Bytes => format!("{}.to_bytes()", name), ParamType::FixedBytes(len) if len <= 32 => { // ensure no panic on slice too small. let read_hash = format!("b.resize({}, 0); ethereum_types::H{}::from_slice(&b[..{}])", len, len * 8, len); format!("{}.to_fixed_bytes().map(|mut b| {{ {} }})", name, read_hash) } ParamType::FixedBytes(_) => format!("{}.to_fixed_bytes()", name), ParamType::Int(width) => { let read_int = match width { 8 => "i[31] as i8".into(), 16 | 32 | 64 => format!("BigEndian::read_i{}(&i[{}..])", width, 32 - (width / 8)), _ => panic!("Signed integers over 64 bytes not allowed."), }; format!("{}.to_int().map(|i| {})", name, read_int) } ParamType::Uint(width) => { let read_uint = match width { 8 => "u[31] as u8".into(), 16 | 32 | 64 => format!("BigEndian::read_u{}(&u[{}..])", width, 32 - (width / 8)), _ => format!("ethereum_types::U{}::from(&u[..])", width), }; format!("{}.to_uint().map(|u| {})", name, read_uint) } ParamType::Bool => format!("{}.to_bool()", name), ParamType::String => format!("{}.to_string()", name), ParamType::Array(kind) => { let read_array = format!("x.into_iter().map(|a| {{ {} }}).collect::>>()", detokenize("a", *kind)); format!("{}.to_array().and_then(|x| {{ {} }})", name, read_array) } ParamType::FixedArray(_, _) => panic!("Fixed-length arrays not supported.") } } #[cfg(test)] mod tests { use ethabi::ParamType; #[test] fn input_types() { assert_eq!(::input_params_codegen(&[]).unwrap().0, ""); assert_eq!(::input_params_codegen(&[ParamType::Address]).unwrap().0, "param_0: ethereum_types::H160, "); assert_eq!(::input_params_codegen(&[ParamType::Address, ParamType::Bytes]).unwrap().0, "param_0: ethereum_types::H160, param_1: Vec, "); } #[test] fn output_types() { assert_eq!(::output_params_codegen(&[]).unwrap().0, "()"); assert_eq!(::output_params_codegen(&[ParamType::Address]).unwrap().0, "(ethereum_types::H160)"); assert_eq!(::output_params_codegen(&[ParamType::Address, ParamType::Array(Box::new(ParamType::Bytes))]).unwrap().0, "(ethereum_types::H160, Vec>)"); } #[test] fn rust_type() { assert_eq!(::rust_type(ParamType::FixedBytes(32)).unwrap(), "ethereum_types::H256"); assert_eq!(::rust_type(ParamType::Array(Box::new(ParamType::FixedBytes(32)))).unwrap(), "Vec"); assert_eq!(::rust_type(ParamType::Uint(64)).unwrap(), "u64"); assert!(::rust_type(ParamType::Uint(63)).is_err()); assert_eq!(::rust_type(ParamType::Int(32)).unwrap(), "i32"); assert_eq!(::rust_type(ParamType::Uint(256)).unwrap(), "ethereum_types::U256"); } // codegen tests will need bootstrapping of some kind. }