Skip to content
This repository was archived by the owner on Jun 21, 2020. It is now read-only.

Ep 748 simplify pub interface #209

Merged
merged 16 commits into from
Sep 22, 2019
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
2 changes: 1 addition & 1 deletion eng-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ categories = ["wasm"]
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", default-features = false }
eng-pwasm-abi = "0.3"
eng-pwasm-abi = "0.3"
1 change: 1 addition & 0 deletions eng-wasm/derive/.rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# override global formatting. i want clean and standard rustfmt.
34 changes: 25 additions & 9 deletions eng-wasm/derive/Cargo.toml
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
[package]
name = "eng-wasm-derive"
version = "0.1.6" # upgrade version in eng-wasm dependancy as well (if needed)
authors = ["moria <[email protected]>"]
authors = ["moria <[email protected]>", "Reuven Podmazo <[email protected]>"]
edition = "2018"
license = "AGPL-3.0"
description = "Enigma library for creating Secret Contracts"
keywords = ["wasm", "webassembly", "blockchain", "sgx", "enigma"]
categories = ["wasm"]

[lib]
proc-macro = true

[dependencies]
eng-wasm = { version = "0.1.6", path = "../" }
proc-macro2 = "0.4"
quote = "0.6"
syn = { version = "0.15", features = ["full"] }
#syn = { version = "0.15", features = ["full", "extra-traits"] } # for debug purposes
failure = { version = "0.1.5", default-features = false, features = ["derive"] }
proc-macro2 = "1.0"
syn = { version = "1.0", features = ["full"] }
quote = "1.0"
failure = "0.1"
parse-display = "0.1"
ethabi = "6.1"
serde_json = "1.0"
tiny-keccak = "1.4"

[lib]
proc-macro = true
[dev-dependencies]
syn = { version = "1.0", features = ["full", "extra-traits"] }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use the extra-traits feature for printing and comparisons of AST objects

eng-wasm = { path = '..' }

[[example]]
name = 'struct-impl'
crate-type = ["cdylib"]

[[example]]
name = 'trait-impl'
crate-type = ["cdylib"]

[[example]]
name = 'trait-impl-rename'
crate-type = ["cdylib"]
21 changes: 21 additions & 0 deletions eng-wasm/derive/examples/struct-impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#![no_std]

use eng_wasm_derive::pub_interface;

struct MyContract;

#[pub_interface]
impl MyContract {
/// constructor
pub fn construct(_x: u32) {}

/// secret contract method
pub fn expand(input: u32) -> u64 {
Self::expand_impl(input)
}

/// private method, not exported from contract
fn expand_impl(input: u32) -> u64 {
input as u64
}
}
29 changes: 29 additions & 0 deletions eng-wasm/derive/examples/trait-impl-rename.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![no_std]

use eng_wasm_derive::pub_interface;

struct MyContractImplementation;

#[pub_interface(MyContractImplementation)]
trait MyContract {
/// constructor
fn construct(_x: u32);

/// secret contract method
fn expand(input: u32) -> u64;
}

impl MyContract for MyContractImplementation {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe add impl MyContract for another structure in addition, just to emphasize the point of the argument to pub_interface. But it is up to you.

fn construct(_x: u32) {}

fn expand(input: u32) -> u64 {
Self::expand_impl(input)
}
}

impl MyContractImplementation {
/// private method, not exported from contract
fn expand_impl(input: u32) -> u64 {
input as u64
}
}
29 changes: 29 additions & 0 deletions eng-wasm/derive/examples/trait-impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#![no_std]

use eng_wasm_derive::pub_interface;

struct Contract;

#[pub_interface]
trait MyContract {
/// constructor
fn construct(_x: u32);

/// secret contract method
fn expand(input: u32) -> u64;
}

impl MyContract for Contract {
fn construct(_x: u32) {}

fn expand(input: u32) -> u64 {
Self::expand_impl(input)
}
}

impl Contract {
/// private method, not exported from contract
fn expand_impl(input: u32) -> u64 {
input as u64
}
}
155 changes: 155 additions & 0 deletions eng-wasm/derive/src/eth_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::fs::File;

use quote::quote;

use ethabi::{Contract, ParamType};

mod errors;
mod ethereum;

use errors::EngWasmError;
use ethereum::short_signature;

trait Write {
fn write(&self) -> String;
fn error(&self) -> String;
}

impl Write for ParamType {
/// Returns string which is a formatted representation of param.
fn write(&self) -> String {
match *self {
ParamType::Address => "Address".to_owned(),
ParamType::Bytes => "Vec<u8>".to_owned(),
ParamType::FixedBytes(len) => format!("u8[{}]", len),
ParamType::Int(len) => match len {
32 | 64 => format!("i{}", len),
_ => panic!("{}", self.error()),
},
ParamType::Uint(len) => match len {
32 | 64 => format!("u{}", len),
256 => "U256".to_owned(),
_ => panic!("{}", self.error()),
},
ParamType::Bool => "bool".to_owned(),
ParamType::String => "String".to_owned(),
ParamType::FixedArray(ref param, len) => format!("{}[{}]", param.write(), len),
ParamType::Array(ref param) => format!("Vec<{}>", param.write()),
}
}
fn error(&self) -> String {
format!("The type {} is not supported", self.to_string())
}
}

struct FunctionAst {
name: syn::Ident,
args_ast_types: Vec<syn::Type>,
args_types: Vec<ParamType>,
}

fn read_contract_file(file_path: String) -> Result<Box<File>, EngWasmError> {
let file = File::open(file_path)?;
let contents = Box::new(file);
Ok(contents)
}

fn generate_eth_functions(
contract: &Contract,
) -> Result<Vec<proc_macro2::TokenStream>, EngWasmError> {
let mut functions: Vec<FunctionAst> = Vec::new();
for function in &contract.functions {
let mut args_ast_types = Vec::new();
for input in &function.1.inputs {
let arg_type: syn::Type = syn::parse_str(&input.kind.clone().write())?;
args_ast_types.push(arg_type);
}
let args_types = function
.1
.inputs
.iter()
.map(|input| input.kind.clone())
.collect();

let name = syn::Ident::new(&function.1.name, proc_macro2::Span::call_site());
functions.push(FunctionAst {
name,
args_types,
args_ast_types,
})
}

let result: Vec<proc_macro2::TokenStream> = functions
.iter()
.map(|function| {
let function_name = &function.name;
let args_ast_types = function.args_ast_types.clone();
let sig_u32 = short_signature(&function_name.to_string(), &function.args_types);
let sig = syn::Lit::Int(syn::LitInt::new(
&format!("{}_u32", sig_u32 as u32),
proc_macro2::Span::call_site(),
));
let args_number = syn::Lit::Int(syn::LitInt::new(
&format!("{}_usize", args_ast_types.len() as usize),
proc_macro2::Span::call_site(),
));
let args_names: Vec<syn::Ident> = function
.args_ast_types
.iter()
.enumerate()
.map(|item| {
let mut arg = String::from("arg");
arg.push_str(item.0.to_string().as_str());
syn::Ident::new(&arg, proc_macro2::Span::call_site())
})
.collect();
let args_names_copy = args_names.clone();
quote! {
fn #function_name(&self, #(#args_names: #args_ast_types),*){
#![allow(unused_mut)]
#![allow(unused_variables)]
let mut payload = Vec::with_capacity(4 + #args_number * 32);
payload.push((#sig >> 24) as u8);
payload.push((#sig >> 16) as u8);
payload.push((#sig >> 8) as u8);
payload.push(#sig as u8);

let mut sink = eng_pwasm_abi::eth::Sink::new(#args_number);
#(sink.push(#args_names_copy);)*
sink.drain_to(&mut payload);
write_ethereum_bridge(&payload, &self.addr);
}
}
})
.collect();

Ok(result)
}

pub fn impl_eth_contract(
args: proc_macro2::TokenStream,
input: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let input_tokens = parse_macro_input2!(input as syn::ItemStruct);
let struct_name = input_tokens.ident;
let file_path = parse_macro_input2!(args as syn::LitStr);
let contents: Box<File> = read_contract_file(file_path.value()).expect("Bad contract file");
let contract = Contract::load(contents).unwrap();
let it: Vec<proc_macro2::TokenStream> = generate_eth_functions(&contract).unwrap();

quote! {
struct #struct_name {
addr: Address,
}
impl EthContract{
fn new(addr_str: /*Address*/&str) -> Self {
use core::str::FromStr;

// Ethereum Addresses need to start with `0x` so we remove the first two characters
let addr = Address::from_str(&addr_str[2..]).expect("Failed converting the address from hex");
EthContract{ addr }
}
#(#it)*
}
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
use failure::Fail;
use std::string::ToString;
use std::io;
use std::{io, string::ToString};
use syn;

#[derive(Debug, Fail)]
pub enum EngWasmError {
#[fail(display = "I/O error: {:?}", error)]
IoError{
error: String,
},
IoError { error: String },
#[fail(display = "Json error: {}", error)]
JsonError {
error: String,
},
JsonError { error: String },
#[fail(display = "Token parse error: {}", error)]
TokenParseError {
error: String,
}
TokenParseError { error: String },
}


impl From<io::Error> for EngWasmError {
fn from(error: io::Error) -> Self {
match error {
_ => EngWasmError::IoError {error: error.to_string()},
EngWasmError::IoError {
error: error.to_string(),
}
}
}


impl From<serde_json::Error> for EngWasmError {
fn from(err: serde_json::Error) -> Self {
match err {
_ => EngWasmError::JsonError {error: err.to_string()},
EngWasmError::JsonError {
error: err.to_string(),
}
}
}

impl From<syn::parse::Error> for EngWasmError {
fn from (err: syn::parse::Error) -> Self {
match err {
_ => EngWasmError::TokenParseError{error: err.to_string()}
fn from(err: syn::parse::Error) -> Self {
EngWasmError::TokenParseError {
error: err.to_string(),
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use ethabi::param_type::{ParamType, Writer};
use tiny_keccak::Keccak;
use ethabi::param_type::{Writer, ParamType};


pub fn short_signature(name: &str, params: &[ParamType]) -> u32/*[u8; 4] */{
pub fn short_signature(name: &str, params: &[ParamType]) -> u32 /*[u8; 4] */ {
let mut result = [0u8; 4];
fill_signature(name, params, &mut result);
u32::from_be_bytes(result)
}

fn fill_signature(name: &str, params: &[ParamType], result: &mut [u8]) {
let types = params.iter()
let types = params
.iter()
.map(Writer::write)
.collect::<Vec<String>>()
.join(",");
Expand Down
9 changes: 9 additions & 0 deletions eng-wasm/derive/src/into_ident.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub trait IntoIdent {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fairly self-explanatory, but if not i can add a docstring. i'm surprised this is not implemented upstream, so we can file a pull request with this to https://github.com/dtolnay/syn

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realised this is probably why it isn't made to be as easy to use as i made it:
https://docs.rs/quote/1.0.2/quote/macro.format_ident.html#panics

fn into_ident(self) -> syn::Ident;
}

impl IntoIdent for &str {
fn into_ident(self) -> syn::Ident {
quote::format_ident!("{}", self)
}
}
Loading