// 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-util`, //! `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. use ethabi::Contract; use ethabi::spec::{Interface, ParamType, Error as AbiError}; use heck::SnakeCase; extern crate ethabi; extern crate heck; /// Errors in generation. #[derive(Debug)] pub enum Error { /// Bad ABI. Abi(AbiError), /// 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::new(Interface::load(abi.as_bytes()).map_err(Error::Abi)?); let functions = generate_functions(&contract)?; Ok(format!(r##" use byteorder::{{BigEndian, ByteOrder}}; use futures::{{future, Future, BoxFuture}}; use ethabi::{{Contract, Interface, Token}}; use util::{{self, Uint}}; pub struct {name} {{ contract: Contract, /// Address to make calls to. pub address: util::Address, }} 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: util::Address) -> Self {{ let contract = Contract::new(Interface::load(ABI.as_bytes()) .expect("ABI checked at generation-time; qed")); {name} {{ contract: contract, address: address, }} }} {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 = function.input_params(); let outputs = function.output_params(); let (input_params, to_tokens) = input_params_codegen(&inputs) .map_err(|bad_type| Error::UnsupportedType(name.into(), bad_type))?; let (output_type, decode_outputs) = output_params_codegen(&outputs) .map_err(|bad_type| Error::UnsupportedType(name.into(), 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: Fn(util::Address, Vec) -> U, U: Future, Error=String> + Send + 'static {{ let function = self.contract.function(r#"{abi_name}"#.to_string()) .expect("function existence checked at compile-time; qed"); let call_addr = self.address; let call_future = match function.encode_call({to_tokens}) {{ Ok(call_data) => (call)(call_addr, call_data), Err(e) => return future::err(format!("Error encoding call: {{:?}}", e)).boxed(), }}; call_future .and_then(move |out| function.decode_output(out).map_err(|e| format!("{{:?}}", e))) .map(::std::collections::VecDeque::from) .and_then(|mut outputs| {decode_outputs}) .boxed() }} "##, abi_name = name, abi_inputs = inputs, abi_outputs = outputs, snake_name = snake_name, params = input_params, 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. // // two pieces of code are generated: the first gives input types for the function signature, // and the second 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), ParamType> { let mut params = 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)); to_tokens.push_str(&format!("tokens.push({{ {} }});", tokenize_code)); } to_tokens.push_str(" tokens }"); Ok((params, 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: VecDeque`, 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 .pop_front() .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 => "util::Address".into(), ParamType::FixedBytes(len) if len <= 32 => format!("util::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!("util::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!("util::U256::from({} as u64)", name) } else { format!("util::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(util::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); util::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 | 16 | 32 | 64 => format!("util::U256(u).low_u64() as u{}", width), _ => format!("util::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::spec::ParamType; #[test] fn input_types() { assert_eq!(::input_params_codegen(&[]).unwrap().0, ""); assert_eq!(::input_params_codegen(&[ParamType::Address]).unwrap().0, "param_0: util::Address, "); assert_eq!(::input_params_codegen(&[ParamType::Address, ParamType::Bytes]).unwrap().0, "param_0: util::Address, param_1: Vec, "); } #[test] fn output_types() { assert_eq!(::output_params_codegen(&[]).unwrap().0, "()"); assert_eq!(::output_params_codegen(&[ParamType::Address]).unwrap().0, "(util::Address)"); assert_eq!(::output_params_codegen(&[ParamType::Address, ParamType::Array(Box::new(ParamType::Bytes))]).unwrap().0, "(util::Address, Vec>)"); } #[test] fn rust_type() { assert_eq!(::rust_type(ParamType::FixedBytes(32)).unwrap(), "util::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(), "util::U256"); } // codegen tests will need bootstrapping of some kind. }