From 8c36ca5083782a4491dbd4f5ed30e3b49dfe7507 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 7 Sep 2023 20:04:00 -0600 Subject: [PATCH 01/17] cheaders: initial --- src/cheader.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 17 +++++- 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/cheader.rs diff --git a/src/cheader.rs b/src/cheader.rs new file mode 100644 index 0000000..a14e0fc --- /dev/null +++ b/src/cheader.rs @@ -0,0 +1,146 @@ +use std::collections::HashMap; +use std::io::BufReader; +use std::fs; +use serde_json::Value; +use eyre::bail; + +pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { + let f = fs::File::open(&in_path)?; + + let input: Value = serde_json::from_reader(BufReader::new(f))?; + + let Some(input_contracts) = input["contracts"].as_object() else { + bail!("did not find top-level contracts object in {}", in_path) + }; + + let mut pathbuf = std::path::PathBuf::new(); + pathbuf.push(out_path); + for (solidity_file_name, solidity_file_out) in input_contracts.iter() { + let debug_path = vec![solidity_file_name.as_str()]; + let Some(contracts) = solidity_file_out.as_object() else { + println!("skipping output for {:?} not an object..", &debug_path); + continue; + }; + pathbuf.push(&solidity_file_name); + fs::create_dir_all(&pathbuf)?; + let mut header_body = String::default(); + for (contract_name, contract_val) in contracts.iter() { + let mut debug_path = debug_path.clone(); + debug_path.push(&contract_name); + let Some(properties) = contract_val.as_object() else { + println!("skipping output for {:?} not an object..", &debug_path); + continue; + }; + + let mut methods :HashMap<&str, Vec<(&str, &str)>> = HashMap::default(); + + debug_path.push("evm"); + if let Some(Value::Object(evm_vals)) = properties.get("evm") { + debug_path.push("methodIdentifiers"); + if let Some(Value::Object(method_map)) = evm_vals.get("methodIdentifiers") { + for (method_name, method_val) in method_map.iter() { + let Some(method_val_str) = method_val.as_str() else { + println!("skipping output for {:?}/{} not a string..", &debug_path, method_name); + continue; + }; + let Some(simple_name_length) = method_name.find('(') else { + println!("skipping output for {:?}/{} no \"(\"..", &debug_path, method_name); + continue; + }; + let simple_name = &method_name[..simple_name_length]; + methods.entry(simple_name).or_insert(Vec::default()).push((method_name.as_str(), method_val_str)); + } + } else { + println!("skipping output for {:?}: not an object..", &debug_path); + } + debug_path.pop(); + } + debug_path.pop(); + + for (simple_name, overloads) in methods { + for (index, (full_name, identifier)) in overloads.iter().enumerate() { + let index_suffix = match index { + 0 => String::default(), + x => format!("_{}",x), + }; + header_body.push_str(format!("#define METHOD_{}{} 0x{} // {}\n", simple_name, index_suffix, identifier, full_name).as_str()) + } + } + + if header_body.len() != 0 { + header_body.push('\n'); + } + debug_path.push("storageLayout"); + if let Some(Value::Object(layout_vals)) = properties.get("storageLayout") { + debug_path.push("storage"); + if let Some(Value::Array(storage_arr)) = layout_vals.get("storage") { + for storage_val in storage_arr.iter() { + let Some(storage_obj) = storage_val.as_object() else { + println!("skipping output inside {:?}: not an object..", &debug_path); + continue; + }; + let Some(Value::String(label)) = storage_obj.get("label") else { + println!("skipping output inside {:?}: no label..", &debug_path); + continue; + }; + let Some(Value::String(slot)) = storage_obj.get("slot") else { + println!("skipping output inside {:?}: no slot..", &debug_path); + continue; + }; + let Some(Value::Number(offset)) = storage_obj.get("offset") else { + println!("skipping output inside {:?}: no offset..", &debug_path); + continue; + }; + header_body.push_str("#define STORAGE_SLOT_"); + header_body.push_str(&label); + header_body.push(' '); + header_body.push_str(&slot); + header_body.push('\n'); + header_body.push_str("#define STORAGE_OFFSET_"); + header_body.push_str(&label); + header_body.push(' '); + header_body.push_str(offset.to_string().as_str()); + header_body.push('\n'); + } + } else { + println!("skipping output for {:?}: not an array..", &debug_path); + } + debug_path.pop(); + } else { + println!("skipping output for {:?}: not an object..", &debug_path); + } + debug_path.pop(); + if header_body.len() != 0 { + let mut unique_identifier = String::from("__"); + unique_identifier.push_str(&solidity_file_name.to_uppercase()); + unique_identifier.push('_'); + unique_identifier.push_str(&contract_name.to_uppercase()); + unique_identifier.push('_'); + + let contents = format!(r#" // autogenerated by cargo-stylus +#ifndef {uniq} +#define {uniq} + +#ifdef __cplusplus +extern "C" {{ +#endif + +{body} + +#ifdef __cplusplus +}} +#endif + +#endif // {uniq} +"#, uniq=unique_identifier, body=header_body); + + let filename :String = contract_name.into(); + pathbuf.push(filename + ".h"); + fs::write(&pathbuf, &contents)?; + pathbuf.pop(); + } + } + pathbuf.pop(); + } + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c7f73a1..144a22f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::{Args, Parser, ValueEnum}; use color::Color; use ethers::types::H160; +mod cheader; mod check; mod color; mod constants; @@ -21,6 +22,15 @@ mod wallet; #[command(bin_name = "cargo")] enum CargoCli { Stylus(StylusArgs), + CHeaders(CHeaderArgs) +} + +#[derive(Parser, Debug)] +#[command(name = "c_header")] +struct CHeaderArgs { + #[arg(required = true)] + input: String, + out_dir: String, } #[derive(Parser, Debug)] @@ -158,7 +168,12 @@ pub struct TxSendingOpts { #[tokio::main] async fn main() -> eyre::Result<()> { - let CargoCli::Stylus(args) = CargoCli::parse(); + let args = match CargoCli::parse() { + CargoCli::Stylus(args) => args, + CargoCli::CHeaders(args) => { + return cheader::c_headers(args.input, args.out_dir); + } + }; match args.command { StylusSubcommands::New { name, minimal } => { From 39f42a1b2b3b4c4288c89ae8702b95974e712d1f Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 8 Sep 2023 10:43:55 -0600 Subject: [PATCH 02/17] cheaders: use alloy to parse functions from abi --- Cargo.lock | 182 ++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/cheader.rs | 40 +++++------ 3 files changed, 196 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c67f6f..afce153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,54 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-json-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe46acf61ad5bd7a5c21839bb2526358382fb8a6526f7ba393bdf593e2ae43f" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f938f00332d63a5b0ac687bd6f46d03884638948921d9f8b50c59563d421ae25" +dependencies = [ + "arrayvec", + "bytes", + "smol_str", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "627a32998aee7a7eedd351e9b6d4cacef9426935219a3a61befa332db1be5ca3" + [[package]] name = "anstream" version = "0.5.0" @@ -391,6 +439,7 @@ dependencies = [ name = "cargo-stylus" version = "0.1.5" dependencies = [ + "alloy-json-abi", "brotli2", "bytes", "bytesize", @@ -578,6 +627,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "corosensei" version = "0.1.4" @@ -858,8 +913,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version", "syn 1.0.109", ] @@ -1727,6 +1784,15 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hmac" @@ -2018,7 +2084,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax", + "regex-syntax 0.7.5", "string_cache", "term", "tiny-keccak", @@ -2049,6 +2115,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -2200,6 +2272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2580,6 +2653,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2600,6 +2693,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -2645,6 +2744,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.7.0" @@ -2717,7 +2825,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] @@ -2728,9 +2836,15 @@ checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" @@ -2882,6 +2996,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ruint" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2972,6 +3106,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.15" @@ -3259,6 +3405,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + [[package]] name = "socket2" version = "0.4.9" @@ -3711,6 +3866,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -3789,12 +3950,27 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.3" diff --git a/Cargo.toml b/Cargo.toml index a5d3695..cd28dbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ clap = { version = "4.3.17", features = [ "derive" ] } eyre = "0.6.8" hex = "0.4.3" serde = { version = "1.0.174", features = ["derive"] } +alloy-json-abi = "0.3.2" bytesize = "1.2.0" ethers = "2.0.8" serde_json = "1.0.103" diff --git a/src/cheader.rs b/src/cheader.rs index a14e0fc..6b8f617 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -3,6 +3,7 @@ use std::io::BufReader; use std::fs; use serde_json::Value; use eyre::bail; +use alloy_json_abi::{JsonAbi, Function}; pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { let f = fs::File::open(&in_path)?; @@ -32,38 +33,29 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { continue; }; - let mut methods :HashMap<&str, Vec<(&str, &str)>> = HashMap::default(); + let mut methods :HashMap> = HashMap::default(); - debug_path.push("evm"); - if let Some(Value::Object(evm_vals)) = properties.get("evm") { - debug_path.push("methodIdentifiers"); - if let Some(Value::Object(method_map)) = evm_vals.get("methodIdentifiers") { - for (method_name, method_val) in method_map.iter() { - let Some(method_val_str) = method_val.as_str() else { - println!("skipping output for {:?}/{} not a string..", &debug_path, method_name); - continue; - }; - let Some(simple_name_length) = method_name.find('(') else { - println!("skipping output for {:?}/{} no \"(\"..", &debug_path, method_name); - continue; - }; - let simple_name = &method_name[..simple_name_length]; - methods.entry(simple_name).or_insert(Vec::default()).push((method_name.as_str(), method_val_str)); - } - } else { - println!("skipping output for {:?}: not an object..", &debug_path); - } - debug_path.pop(); + if let Some(raw) = properties.get("abi") { + // Sadly, JsonAbi = serde_json::from_value is not supported. + // Tonight, we hack! + let abi_json = serde_json::to_string(raw)?; + let abi:JsonAbi = serde_json::from_str(&abi_json)?; + for function in abi.functions() { + let name = function.name.clone(); + methods.entry(name).or_insert(Vec::default()).push(function.clone()); + } + } else { + println!("skipping abi for {:?}: not found", &debug_path); } - debug_path.pop(); for (simple_name, overloads) in methods { - for (index, (full_name, identifier)) in overloads.iter().enumerate() { + for (index, overload) in overloads.iter().enumerate() { let index_suffix = match index { 0 => String::default(), x => format!("_{}",x), }; - header_body.push_str(format!("#define METHOD_{}{} 0x{} // {}\n", simple_name, index_suffix, identifier, full_name).as_str()) + let selector = u32::from_be_bytes(overload.selector()); + header_body.push_str(format!("#define METHOD_{}{} 0x{:08x} // {}\n", simple_name, index_suffix, selector, overload.signature()).as_str()) } } From a68111a62b3062274c4548de690ec72ffb2d43af Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 8 Sep 2023 11:30:59 -0600 Subject: [PATCH 03/17] cheaders: router body --- src/cheader.rs | 51 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/src/cheader.rs b/src/cheader.rs index 6b8f617..35d0ed0 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -24,7 +24,6 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { }; pathbuf.push(&solidity_file_name); fs::create_dir_all(&pathbuf)?; - let mut header_body = String::default(); for (contract_name, contract_val) in contracts.iter() { let mut debug_path = debug_path.clone(); debug_path.push(&contract_name); @@ -48,14 +47,20 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { println!("skipping abi for {:?}: not found", &debug_path); } - for (simple_name, overloads) in methods { + let mut header_body = String::default(); + let mut router_body = String::default(); + + for (simple_name, mut overloads) in methods { + overloads.sort_by(|a, b| a.signature().cmp(&b.signature())); for (index, overload) in overloads.iter().enumerate() { - let index_suffix = match index { - 0 => String::default(), - x => format!("_{}",x), + let c_name = match index { + 0 => simple_name.clone(), + x => format!("{}_{}",simple_name, x), }; let selector = u32::from_be_bytes(overload.selector()); - header_body.push_str(format!("#define METHOD_{}{} 0x{:08x} // {}\n", simple_name, index_suffix, selector, overload.signature()).as_str()) + header_body.push_str(format!("#define SELECTOR_{} 0x{:08x} // {}\n", c_name, selector, overload.signature()).as_str()); + header_body.push_str(format!("ArbResult {}(uint8_t *input, size_t len); // {}\n", c_name, overload.signature()).as_str()); + router_body.push_str(format!(" if (selector==SELECTOR_{}) return {}(input, len);\n", c_name, c_name).as_str()); } } @@ -113,6 +118,8 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { #ifndef {uniq} #define {uniq} +#include + #ifdef __cplusplus extern "C" {{ #endif @@ -124,13 +131,43 @@ extern "C" {{ #endif #endif // {uniq} -"#, uniq=unique_identifier, body=header_body); +"# + ,uniq=unique_identifier, body=header_body); let filename :String = contract_name.into(); pathbuf.push(filename + ".h"); fs::write(&pathbuf, &contents)?; pathbuf.pop(); } + if router_body.len() != 0 { + let contents = format!(r#" // autogenerated by cargo-stylus + +#include "{contract}.h" +#include +#include + +ArbResult {contract}_entry(uint8_t *input, size_t len) {{ + ArbResult err = {{Failure, 0, 0}}; + if (len < 4) {{ + return err; + }} + uint32_t selector = bebi_get_u32(input, 0); + input +=4; + len -=4; +{body} + return err; +}} + +ENTRYPOINT({contract}_entry) +"# + ,contract=contract_name, body=router_body); + + let filename :String = contract_name.into(); + pathbuf.push(filename + "_main.c"); + fs::write(&pathbuf, &contents)?; + pathbuf.pop(); + + } } pathbuf.pop(); } From 249681d864bc4b0f251bf47494b47e3b621a975b Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 11 Sep 2023 18:53:31 -0600 Subject: [PATCH 04/17] cheaders: smarter slot defines and cargofmt --- Cargo.lock | 1 + Cargo.toml | 1 + src/cheader.rs | 118 +++++++++++++++++++++++++++++++++++-------------- src/main.rs | 2 +- 4 files changed, 88 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afce153..0cfe4fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,6 +451,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "tiny-keccak", "tokio", "wasmer", ] diff --git a/Cargo.toml b/Cargo.toml index cd28dbe..5dcbe7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ serde_json = "1.0.103" tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread" ] } wasmer = "3.1.0" thiserror = "1.0.47" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } diff --git a/src/cheader.rs b/src/cheader.rs index 35d0ed0..8533c76 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -1,15 +1,16 @@ +use alloy_json_abi::{Function, JsonAbi}; +use eyre::bail; +use serde_json::Value; use std::collections::HashMap; -use std::io::BufReader; use std::fs; -use serde_json::Value; -use eyre::bail; -use alloy_json_abi::{JsonAbi, Function}; +use std::io::BufReader; +use tiny_keccak::{Hasher, Keccak}; -pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { +pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { let f = fs::File::open(&in_path)?; let input: Value = serde_json::from_reader(BufReader::new(f))?; - + let Some(input_contracts) = input["contracts"].as_object() else { bail!("did not find top-level contracts object in {}", in_path) }; @@ -31,20 +32,23 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { println!("skipping output for {:?} not an object..", &debug_path); continue; }; - - let mut methods :HashMap> = HashMap::default(); + + let mut methods: HashMap> = HashMap::default(); if let Some(raw) = properties.get("abi") { // Sadly, JsonAbi = serde_json::from_value is not supported. // Tonight, we hack! let abi_json = serde_json::to_string(raw)?; - let abi:JsonAbi = serde_json::from_str(&abi_json)?; + let abi: JsonAbi = serde_json::from_str(&abi_json)?; for function in abi.functions() { let name = function.name.clone(); - methods.entry(name).or_insert(Vec::default()).push(function.clone()); - } + methods + .entry(name) + .or_insert(Vec::default()) + .push(function.clone()); + } } else { - println!("skipping abi for {:?}: not found", &debug_path); + println!("skipping abi for {:?}: not found", &debug_path); } let mut header_body = String::default(); @@ -55,12 +59,33 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { for (index, overload) in overloads.iter().enumerate() { let c_name = match index { 0 => simple_name.clone(), - x => format!("{}_{}",simple_name, x), + x => format!("{}_{}", simple_name, x), }; let selector = u32::from_be_bytes(overload.selector()); - header_body.push_str(format!("#define SELECTOR_{} 0x{:08x} // {}\n", c_name, selector, overload.signature()).as_str()); - header_body.push_str(format!("ArbResult {}(uint8_t *input, size_t len); // {}\n", c_name, overload.signature()).as_str()); - router_body.push_str(format!(" if (selector==SELECTOR_{}) return {}(input, len);\n", c_name, c_name).as_str()); + header_body.push_str( + format!( + "#define SELECTOR_{} 0x{:08x} // {}\n", + c_name, + selector, + overload.signature() + ) + .as_str(), + ); + header_body.push_str( + format!( + "ArbResult {}(uint8_t *input, size_t len); // {}\n", + c_name, + overload.signature() + ) + .as_str(), + ); + router_body.push_str( + format!( + " if (selector==SELECTOR_{}) return {}(input, len);\n", + c_name, c_name + ) + .as_str(), + ); } } @@ -84,14 +109,36 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { println!("skipping output inside {:?}: no slot..", &debug_path); continue; }; + let Ok(slot) = slot.parse::() else { + println!("skipping output inside {:?}: slot not u64 ..", &debug_path); + continue; + }; + let Some(Value::String(val_type)) = storage_obj.get("type") else { + println!("skipping output inside {:?}: no type..", &debug_path); + continue; + }; let Some(Value::Number(offset)) = storage_obj.get("offset") else { println!("skipping output inside {:?}: no offset..", &debug_path); continue; }; + let mut slot_buf = vec![0u8; 32 - 8]; + slot_buf.extend(slot.to_be_bytes()); + if val_type.starts_with("t_array(") && val_type.ends_with(")dyn_storage") { + let mut keccak = Keccak::v256(); + keccak.update(&slot_buf); + keccak.finalize(&mut slot_buf); + } + let slot_strings: Vec = slot_buf + .iter() + .map(|input| -> String { format!("0x{:02x}", input) }) + .collect(); + let slot_vals = slot_strings.join(", "); header_body.push_str("#define STORAGE_SLOT_"); header_body.push_str(&label); - header_body.push(' '); - header_body.push_str(&slot); + header_body.push_str(" {"); + header_body.push_str(slot_vals.as_str()); + header_body.push_str("} // "); + header_body.push_str(val_type); header_body.push('\n'); header_body.push_str("#define STORAGE_OFFSET_"); header_body.push_str(&label); @@ -114,7 +161,8 @@ pub fn c_headers(in_path: String, out_path: String) ->eyre::Result<()> { unique_identifier.push_str(&contract_name.to_uppercase()); unique_identifier.push('_'); - let contents = format!(r#" // autogenerated by cargo-stylus + let contents = format!( + r#" // autogenerated by cargo-stylus #ifndef {uniq} #define {uniq} @@ -131,16 +179,19 @@ extern "C" {{ #endif #endif // {uniq} -"# - ,uniq=unique_identifier, body=header_body); +"#, + uniq = unique_identifier, + body = header_body + ); - let filename :String = contract_name.into(); + let filename: String = contract_name.into(); pathbuf.push(filename + ".h"); fs::write(&pathbuf, &contents)?; - pathbuf.pop(); + pathbuf.pop(); } if router_body.len() != 0 { - let contents = format!(r#" // autogenerated by cargo-stylus + let contents = format!( + r#" // autogenerated by cargo-stylus #include "{contract}.h" #include @@ -159,17 +210,18 @@ ArbResult {contract}_entry(uint8_t *input, size_t len) {{ }} ENTRYPOINT({contract}_entry) -"# - ,contract=contract_name, body=router_body); +"#, + contract = contract_name, + body = router_body + ); - let filename :String = contract_name.into(); - pathbuf.push(filename + "_main.c"); - fs::write(&pathbuf, &contents)?; - pathbuf.pop(); - - } + let filename: String = contract_name.into(); + pathbuf.push(filename + "_main.c"); + fs::write(&pathbuf, &contents)?; + pathbuf.pop(); + } } pathbuf.pop(); } Ok(()) -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 144a22f..e4e6888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ mod wallet; #[command(bin_name = "cargo")] enum CargoCli { Stylus(StylusArgs), - CHeaders(CHeaderArgs) + CHeaders(CHeaderArgs), } #[derive(Parser, Debug)] From 47831f540965f20bfc23bcca422f340e677d7487 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 13 Sep 2023 19:36:09 -0600 Subject: [PATCH 05/17] cheaders: test function mutability --- src/cheader.rs | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/src/cheader.rs b/src/cheader.rs index 8533c76..bd1e404 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -1,4 +1,4 @@ -use alloy_json_abi::{Function, JsonAbi}; +use alloy_json_abi::{Function, JsonAbi, StateMutability}; use eyre::bail; use serde_json::Value; use std::collections::HashMap; @@ -62,6 +62,12 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { x => format!("{}_{}", simple_name, x), }; let selector = u32::from_be_bytes(overload.selector()); + let (hdr_params, call_params, payable) = match overload.state_mutability { + StateMutability::Pure => ("(uint8_t *input, size_t len)", "(input, len)", false), + StateMutability::View => ("(const void *storage, uint8_t *input, size_t len)", "(NULL, input, len)", false), + StateMutability::NonPayable => ("(void *storage, uint8_t *input, size_t len)", "(NULL, input, len)", false), + StateMutability::Payable => ("(void *storage, uint8_t *input, size_t len, bebi32 value)", "(NULL, input, len, value)", true), + }; header_body.push_str( format!( "#define SELECTOR_{} 0x{:08x} // {}\n", @@ -73,16 +79,32 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { ); header_body.push_str( format!( - "ArbResult {}(uint8_t *input, size_t len); // {}\n", + "ArbResult {}{}; // {}\n", c_name, + hdr_params, overload.signature() ) .as_str(), ); router_body.push_str( format!( - " if (selector==SELECTOR_{}) return {}(input, len);\n", - c_name, c_name + " if (selector==SELECTOR_{}) {{\n", + c_name, + ) + .as_str(), + ); + if !payable { + router_body.push_str( + format!( + " if (!bebi32_is_0(value)) return _return_nodata(Failure);\n", + ) + .as_str(), + ); + } + router_body.push_str( + format!( + " return {}{};\n }}\n", + c_name, call_params ) .as_str(), ); @@ -167,11 +189,14 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { #define {uniq} #include +#include #ifdef __cplusplus extern "C" {{ #endif +ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); + {body} #ifdef __cplusplus @@ -196,17 +221,22 @@ extern "C" {{ #include "{contract}.h" #include #include +#include + ArbResult {contract}_entry(uint8_t *input, size_t len) {{ - ArbResult err = {{Failure, 0, 0}}; + bebi32 value; + msg_value(value); if (len < 4) {{ - return err; + return default_func(NULL, input, len, value); }} uint32_t selector = bebi_get_u32(input, 0); input +=4; len -=4; {body} - return err; + input -=4; + len +=4; + return default_func(NULL, input, len, value); }} ENTRYPOINT({contract}_entry) From fb65d5c09faae40f7aea7d19130f4bee9c3fb9e2 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 13 Sep 2023 19:49:22 -0600 Subject: [PATCH 06/17] cheaders: add comment --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index e4e6888..924ab55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ mod wallet; #[command(bin_name = "cargo")] enum CargoCli { Stylus(StylusArgs), - CHeaders(CHeaderArgs), + CHeaders(CHeaderArgs), // not behind the stylus command, to hide it from rust-developers. } #[derive(Parser, Debug)] From 275aa85a0c01652af7f4bd0ff7be5aa9c2e3ef4e Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Wed, 13 Sep 2023 19:58:40 -0600 Subject: [PATCH 07/17] cargo fmt --- src/cheader.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/cheader.rs b/src/cheader.rs index bd1e404..279e66f 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -63,10 +63,24 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { }; let selector = u32::from_be_bytes(overload.selector()); let (hdr_params, call_params, payable) = match overload.state_mutability { - StateMutability::Pure => ("(uint8_t *input, size_t len)", "(input, len)", false), - StateMutability::View => ("(const void *storage, uint8_t *input, size_t len)", "(NULL, input, len)", false), - StateMutability::NonPayable => ("(void *storage, uint8_t *input, size_t len)", "(NULL, input, len)", false), - StateMutability::Payable => ("(void *storage, uint8_t *input, size_t len, bebi32 value)", "(NULL, input, len, value)", true), + StateMutability::Pure => { + ("(uint8_t *input, size_t len)", "(input, len)", false) + } + StateMutability::View => ( + "(const void *storage, uint8_t *input, size_t len)", + "(NULL, input, len)", + false, + ), + StateMutability::NonPayable => ( + "(void *storage, uint8_t *input, size_t len)", + "(NULL, input, len)", + false, + ), + StateMutability::Payable => ( + "(void *storage, uint8_t *input, size_t len, bebi32 value)", + "(NULL, input, len, value)", + true, + ), }; header_body.push_str( format!( @@ -86,27 +100,18 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { ) .as_str(), ); - router_body.push_str( - format!( - " if (selector==SELECTOR_{}) {{\n", - c_name, - ) - .as_str(), - ); + router_body + .push_str(format!(" if (selector==SELECTOR_{}) {{\n", c_name,).as_str()); if !payable { router_body.push_str( format!( " if (!bebi32_is_0(value)) return _return_nodata(Failure);\n", ) .as_str(), - ); + ); } router_body.push_str( - format!( - " return {}{};\n }}\n", - c_name, call_params - ) - .as_str(), + format!(" return {}{};\n }}\n", c_name, call_params).as_str(), ); } } From fc0ec77e51191c0579a1e03beb3c8d415266bc31 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 14 Sep 2023 15:04:08 -0600 Subject: [PATCH 08/17] cheaders: update stylus lib --- src/cheader.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/cheader.rs b/src/cheader.rs index 279e66f..089e9c6 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -104,10 +104,7 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { .push_str(format!(" if (selector==SELECTOR_{}) {{\n", c_name,).as_str()); if !payable { router_body.push_str( - format!( - " if (!bebi32_is_0(value)) return _return_nodata(Failure);\n", - ) - .as_str(), + format!(" if (!bebi32_is_0(value)) revert();\n",).as_str(), ); } router_body.push_str( @@ -193,7 +190,7 @@ pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { #ifndef {uniq} #define {uniq} -#include +#include #include #ifdef __cplusplus @@ -224,9 +221,9 @@ ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); r#" // autogenerated by cargo-stylus #include "{contract}.h" -#include +#include +#include #include -#include ArbResult {contract}_entry(uint8_t *input, size_t len) {{ From 9f8bbab00663bdf7c09bd650fbea733f304560ff Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 14 Sep 2023 15:08:13 -0600 Subject: [PATCH 09/17] rename cheaders to cgen --- src/cgen.rs | 259 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cheader.rs | 2 +- src/main.rs | 10 +- 3 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 src/cgen.rs diff --git a/src/cgen.rs b/src/cgen.rs new file mode 100644 index 0000000..8e29861 --- /dev/null +++ b/src/cgen.rs @@ -0,0 +1,259 @@ +use alloy_json_abi::{Function, JsonAbi, StateMutability}; +use eyre::bail; +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::io::BufReader; +use tiny_keccak::{Hasher, Keccak}; + +pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { + let f = fs::File::open(&in_path)?; + + let input: Value = serde_json::from_reader(BufReader::new(f))?; + + let Some(input_contracts) = input["contracts"].as_object() else { + bail!("did not find top-level contracts object in {}", in_path) + }; + + let mut pathbuf = std::path::PathBuf::new(); + pathbuf.push(out_path); + for (solidity_file_name, solidity_file_out) in input_contracts.iter() { + let debug_path = vec![solidity_file_name.as_str()]; + let Some(contracts) = solidity_file_out.as_object() else { + println!("skipping output for {:?} not an object..", &debug_path); + continue; + }; + pathbuf.push(&solidity_file_name); + fs::create_dir_all(&pathbuf)?; + for (contract_name, contract_val) in contracts.iter() { + let mut debug_path = debug_path.clone(); + debug_path.push(&contract_name); + let Some(properties) = contract_val.as_object() else { + println!("skipping output for {:?} not an object..", &debug_path); + continue; + }; + + let mut methods: HashMap> = HashMap::default(); + + if let Some(raw) = properties.get("abi") { + // Sadly, JsonAbi = serde_json::from_value is not supported. + // Tonight, we hack! + let abi_json = serde_json::to_string(raw)?; + let abi: JsonAbi = serde_json::from_str(&abi_json)?; + for function in abi.functions() { + let name = function.name.clone(); + methods + .entry(name) + .or_insert(Vec::default()) + .push(function.clone()); + } + } else { + println!("skipping abi for {:?}: not found", &debug_path); + } + + let mut header_body = String::default(); + let mut router_body = String::default(); + + for (simple_name, mut overloads) in methods { + overloads.sort_by(|a, b| a.signature().cmp(&b.signature())); + for (index, overload) in overloads.iter().enumerate() { + let c_name = match index { + 0 => simple_name.clone(), + x => format!("{}_{}", simple_name, x), + }; + let selector = u32::from_be_bytes(overload.selector()); + let (hdr_params, call_params, payable) = match overload.state_mutability { + StateMutability::Pure => { + ("(uint8_t *input, size_t len)", "(input, len)", false) + } + StateMutability::View => ( + "(const void *storage, uint8_t *input, size_t len)", + "(NULL, input, len)", + false, + ), + StateMutability::NonPayable => ( + "(void *storage, uint8_t *input, size_t len)", + "(NULL, input, len)", + false, + ), + StateMutability::Payable => ( + "(void *storage, uint8_t *input, size_t len, bebi32 value)", + "(NULL, input, len, value)", + true, + ), + }; + header_body.push_str( + format!( + "#define SELECTOR_{} 0x{:08x} // {}\n", + c_name, + selector, + overload.signature() + ) + .as_str(), + ); + header_body.push_str( + format!( + "ArbResult {}{}; // {}\n", + c_name, + hdr_params, + overload.signature() + ) + .as_str(), + ); + router_body + .push_str(format!(" if (selector==SELECTOR_{}) {{\n", c_name,).as_str()); + if !payable { + router_body.push_str( + format!(" if (!bebi32_is_0(value)) revert();\n",).as_str(), + ); + } + router_body.push_str( + format!(" return {}{};\n }}\n", c_name, call_params).as_str(), + ); + } + } + + if header_body.len() != 0 { + header_body.push('\n'); + } + debug_path.push("storageLayout"); + if let Some(Value::Object(layout_vals)) = properties.get("storageLayout") { + debug_path.push("storage"); + if let Some(Value::Array(storage_arr)) = layout_vals.get("storage") { + for storage_val in storage_arr.iter() { + let Some(storage_obj) = storage_val.as_object() else { + println!("skipping output inside {:?}: not an object..", &debug_path); + continue; + }; + let Some(Value::String(label)) = storage_obj.get("label") else { + println!("skipping output inside {:?}: no label..", &debug_path); + continue; + }; + let Some(Value::String(slot)) = storage_obj.get("slot") else { + println!("skipping output inside {:?}: no slot..", &debug_path); + continue; + }; + let Ok(slot) = slot.parse::() else { + println!("skipping output inside {:?}: slot not u64 ..", &debug_path); + continue; + }; + let Some(Value::String(val_type)) = storage_obj.get("type") else { + println!("skipping output inside {:?}: no type..", &debug_path); + continue; + }; + let Some(Value::Number(offset)) = storage_obj.get("offset") else { + println!("skipping output inside {:?}: no offset..", &debug_path); + continue; + }; + let mut slot_buf = vec![0u8; 32 - 8]; + slot_buf.extend(slot.to_be_bytes()); + if val_type.starts_with("t_array(") && val_type.ends_with(")dyn_storage") { + let mut keccak = Keccak::v256(); + keccak.update(&slot_buf); + keccak.finalize(&mut slot_buf); + } + let slot_strings: Vec = slot_buf + .iter() + .map(|input| -> String { format!("0x{:02x}", input) }) + .collect(); + let slot_vals = slot_strings.join(", "); + header_body.push_str("#define STORAGE_SLOT_"); + header_body.push_str(&label); + header_body.push_str(" {"); + header_body.push_str(slot_vals.as_str()); + header_body.push_str("} // "); + header_body.push_str(val_type); + header_body.push('\n'); + header_body.push_str("#define STORAGE_OFFSET_"); + header_body.push_str(&label); + header_body.push(' '); + header_body.push_str(offset.to_string().as_str()); + header_body.push('\n'); + } + } else { + println!("skipping output for {:?}: not an array..", &debug_path); + } + debug_path.pop(); + } else { + println!("skipping output for {:?}: not an object..", &debug_path); + } + debug_path.pop(); + if header_body.len() != 0 { + let mut unique_identifier = String::from("__"); + unique_identifier.push_str(&solidity_file_name.to_uppercase()); + unique_identifier.push('_'); + unique_identifier.push_str(&contract_name.to_uppercase()); + unique_identifier.push('_'); + + let contents = format!( + r#" // autogenerated by cargo-stylus +#ifndef {uniq} +#define {uniq} + +#include +#include + +#ifdef __cplusplus +extern "C" {{ +#endif + +ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); + +{body} + +#ifdef __cplusplus +}} +#endif + +#endif // {uniq} +"#, + uniq = unique_identifier, + body = header_body + ); + + let filename: String = contract_name.into(); + pathbuf.push(filename + ".h"); + fs::write(&pathbuf, &contents)?; + pathbuf.pop(); + } + if router_body.len() != 0 { + let contents = format!( + r#" // autogenerated by cargo-stylus + +#include "{contract}.h" +#include +#include +#include + + +ArbResult {contract}_entry(uint8_t *input, size_t len) {{ + bebi32 value; + msg_value(value); + if (len < 4) {{ + return default_func(NULL, input, len, value); + }} + uint32_t selector = bebi_get_u32(input, 0); + input +=4; + len -=4; +{body} + input -=4; + len +=4; + return default_func(NULL, input, len, value); +}} + +ENTRYPOINT({contract}_entry) +"#, + contract = contract_name, + body = router_body + ); + + let filename: String = contract_name.into(); + pathbuf.push(filename + "_main.c"); + fs::write(&pathbuf, &contents)?; + pathbuf.pop(); + } + } + pathbuf.pop(); + } + Ok(()) +} diff --git a/src/cheader.rs b/src/cheader.rs index 089e9c6..8e29861 100644 --- a/src/cheader.rs +++ b/src/cheader.rs @@ -6,7 +6,7 @@ use std::fs; use std::io::BufReader; use tiny_keccak::{Hasher, Keccak}; -pub fn c_headers(in_path: String, out_path: String) -> eyre::Result<()> { +pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let f = fs::File::open(&in_path)?; let input: Value = serde_json::from_reader(BufReader::new(f))?; diff --git a/src/main.rs b/src/main.rs index 924ab55..cec282a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use clap::{Args, Parser, ValueEnum}; use color::Color; use ethers::types::H160; -mod cheader; +mod cgen; mod check; mod color; mod constants; @@ -22,12 +22,12 @@ mod wallet; #[command(bin_name = "cargo")] enum CargoCli { Stylus(StylusArgs), - CHeaders(CHeaderArgs), // not behind the stylus command, to hide it from rust-developers. + CGen(CGenArgs), // not behind the stylus command, to hide it from rust-developers. } #[derive(Parser, Debug)] #[command(name = "c_header")] -struct CHeaderArgs { +struct CGenArgs { #[arg(required = true)] input: String, out_dir: String, @@ -170,8 +170,8 @@ pub struct TxSendingOpts { async fn main() -> eyre::Result<()> { let args = match CargoCli::parse() { CargoCli::Stylus(args) => args, - CargoCli::CHeaders(args) => { - return cheader::c_headers(args.input, args.out_dir); + CargoCli::CGen(args) => { + return cgen::c_gen(args.input, args.out_dir); } }; From 877e712f1c5c315e884fdfdae5e01edda24000d7 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Thu, 14 Sep 2023 15:09:41 -0600 Subject: [PATCH 10/17] c_generate: rename command --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index cec282a..49a008a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ enum CargoCli { } #[derive(Parser, Debug)] -#[command(name = "c_header")] +#[command(name = "c_generate")] struct CGenArgs { #[arg(required = true)] input: String, From 7e8d6480d0e295d8bdf9482033b9bdbb2feded66 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 15 Sep 2023 18:14:37 -0600 Subject: [PATCH 11/17] cgen: improve slot/offset generation --- src/cgen.rs | 85 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/src/cgen.rs b/src/cgen.rs index 8e29861..5f1c34d 100644 --- a/src/cgen.rs +++ b/src/cgen.rs @@ -6,6 +6,14 @@ use std::fs; use std::io::BufReader; use tiny_keccak::{Hasher, Keccak}; +fn c_bytearray_initializer(val: &[u8]) -> String { + let slot_strings: Vec = val + .iter() + .map(|input| -> String { format!("0x{:02x}", input) }) + .collect(); + format!("{{{}}}", slot_strings.join(", ")) +} + pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let f = fs::File::open(&in_path)?; @@ -141,34 +149,67 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { println!("skipping output inside {:?}: no type..", &debug_path); continue; }; - let Some(Value::Number(offset)) = storage_obj.get("offset") else { + let Some(Value::Number(read_offset)) = storage_obj.get("offset") else { println!("skipping output inside {:?}: no offset..", &debug_path); continue; }; + let offset = match read_offset.as_i64() { + None => { + println!( + "skipping output inside {:?}: unexpected offset..", + &debug_path + ); + continue; + } + Some(num) => { + if num > 32 || num < 0 { + println!( + "skipping output inside {:?}: unexpected offset..", + &debug_path + ); + continue; + }; + 32 - num + } + }; let mut slot_buf = vec![0u8; 32 - 8]; slot_buf.extend(slot.to_be_bytes()); - if val_type.starts_with("t_array(") && val_type.ends_with(")dyn_storage") { - let mut keccak = Keccak::v256(); - keccak.update(&slot_buf); - keccak.finalize(&mut slot_buf); + + header_body.push_str( + format!( + "#define STORAGE_SLOT_{} {} // {}\n", + &label, + c_bytearray_initializer(&slot_buf), + val_type + ) + .as_str(), + ); + if val_type.starts_with("t_array(") { + if val_type.ends_with(")dyn_storage") { + let mut keccak = Keccak::v256(); + keccak.update(&slot_buf); + keccak.finalize(&mut slot_buf); + header_body.push_str( + format!( + "#define STORAGE_BASE_{} {} // {}\n", + &label, + c_bytearray_initializer(&slot_buf), + val_type + ) + .as_str(), + ); + } + } else if !val_type.starts_with("t_mapping") { + header_body.push_str( + format!( + "#define STORAGE_BE_OFFSET_{} {} // {}\n", + &label, + offset.to_string(), + val_type + ) + .as_str(), + ); } - let slot_strings: Vec = slot_buf - .iter() - .map(|input| -> String { format!("0x{:02x}", input) }) - .collect(); - let slot_vals = slot_strings.join(", "); - header_body.push_str("#define STORAGE_SLOT_"); - header_body.push_str(&label); - header_body.push_str(" {"); - header_body.push_str(slot_vals.as_str()); - header_body.push_str("} // "); - header_body.push_str(val_type); - header_body.push('\n'); - header_body.push_str("#define STORAGE_OFFSET_"); - header_body.push_str(&label); - header_body.push(' '); - header_body.push_str(offset.to_string().as_str()); - header_body.push('\n'); } } else { println!("skipping output for {:?}: not an array..", &debug_path); From 69b668c8249090251bc9cdcc87956f80f6959b21 Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Fri, 15 Sep 2023 18:25:52 -0600 Subject: [PATCH 12/17] cgen: storage_end defines --- src/cgen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cgen.rs b/src/cgen.rs index 5f1c34d..ac8c2c4 100644 --- a/src/cgen.rs +++ b/src/cgen.rs @@ -202,7 +202,7 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { } else if !val_type.starts_with("t_mapping") { header_body.push_str( format!( - "#define STORAGE_BE_OFFSET_{} {} // {}\n", + "#define STORAGE_END_OFFSET_{} {} // {}\n", &label, offset.to_string(), val_type From e1ed287a66af8a13dd61a25ca0be60661a5776ea Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 17 Sep 2023 12:43:02 -0600 Subject: [PATCH 13/17] move C code into module --- src/{cgen.rs => c/gen.rs} | 0 src/{cheader.rs => c/header.rs} | 0 src/c/mod.rs | 5 +++++ src/main.rs | 4 ++-- 4 files changed, 7 insertions(+), 2 deletions(-) rename src/{cgen.rs => c/gen.rs} (100%) rename src/{cheader.rs => c/header.rs} (100%) create mode 100644 src/c/mod.rs diff --git a/src/cgen.rs b/src/c/gen.rs similarity index 100% rename from src/cgen.rs rename to src/c/gen.rs diff --git a/src/cheader.rs b/src/c/header.rs similarity index 100% rename from src/cheader.rs rename to src/c/header.rs diff --git a/src/c/mod.rs b/src/c/mod.rs new file mode 100644 index 0000000..38c0601 --- /dev/null +++ b/src/c/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md + +pub mod gen; +pub mod header; diff --git a/src/main.rs b/src/main.rs index fc55efe..7f83e13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use clap::{Args, Parser, ValueEnum}; use color::Color; use ethers::types::H160; -mod cgen; +mod c; mod check; mod color; mod constants; @@ -171,7 +171,7 @@ async fn main() -> eyre::Result<()> { let args = match CargoCli::parse() { CargoCli::Stylus(args) => args, CargoCli::CGen(args) => { - return cgen::c_gen(args.input, args.out_dir); + return c::gen::c_gen(args.input, args.out_dir); } }; From b736882df5be1cfa7b63bc218d29827ba9f41819 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 17 Sep 2023 12:58:02 -0600 Subject: [PATCH 14/17] cleanup --- src/c/gen.rs | 100 ++++++++++++++++-------------------------------- src/c/header.rs | 80 ++++++++++++++++---------------------- 2 files changed, 68 insertions(+), 112 deletions(-) diff --git a/src/c/gen.rs b/src/c/gen.rs index ac8c2c4..2d7adee 100644 --- a/src/c/gen.rs +++ b/src/c/gen.rs @@ -31,11 +31,13 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { println!("skipping output for {:?} not an object..", &debug_path); continue; }; - pathbuf.push(&solidity_file_name); + pathbuf.push(solidity_file_name); fs::create_dir_all(&pathbuf)?; + for (contract_name, contract_val) in contracts.iter() { let mut debug_path = debug_path.clone(); - debug_path.push(&contract_name); + debug_path.push(contract_name); + let Some(properties) = contract_val.as_object() else { println!("skipping output for {:?} not an object..", &debug_path); continue; @@ -63,7 +65,8 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let mut router_body = String::default(); for (simple_name, mut overloads) in methods { - overloads.sort_by(|a, b| a.signature().cmp(&b.signature())); + overloads.sort_by_key(|a| a.signature()); + for (index, overload) in overloads.iter().enumerate() { let c_name = match index { 0 => simple_name.clone(), @@ -90,38 +93,21 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { true, ), }; - header_body.push_str( - format!( - "#define SELECTOR_{} 0x{:08x} // {}\n", - c_name, - selector, - overload.signature() - ) - .as_str(), - ); - header_body.push_str( - format!( - "ArbResult {}{}; // {}\n", - c_name, - hdr_params, - overload.signature() - ) - .as_str(), - ); - router_body - .push_str(format!(" if (selector==SELECTOR_{}) {{\n", c_name,).as_str()); + + let sig = &overload.signature(); + + header_body += + &format!("#define SELECTOR_{c_name} 0x{selector:08x} // {sig}\n"); + header_body += &format!("ArbResult {c_name}{hdr_params}; // {sig}\n"); + router_body += &format!(" if (selector==SELECTOR_{c_name}) {{\n"); if !payable { - router_body.push_str( - format!(" if (!bebi32_is_0(value)) revert();\n",).as_str(), - ); + router_body += " if (!bebi32_is_0(value)) revert();\n"; } - router_body.push_str( - format!(" return {}{};\n }}\n", c_name, call_params).as_str(), - ); + router_body += &format!(" return {c_name}{call_params};\n }}\n"); } } - if header_body.len() != 0 { + if !header_body.is_empty() { header_body.push('\n'); } debug_path.push("storageLayout"); @@ -156,16 +142,14 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let offset = match read_offset.as_i64() { None => { println!( - "skipping output inside {:?}: unexpected offset..", - &debug_path + "skipping output inside {debug_path:?}: unexpected offset..", ); continue; } Some(num) => { - if num > 32 || num < 0 { + if !(0..=32).contains(&num) { println!( - "skipping output inside {:?}: unexpected offset..", - &debug_path + "skipping output inside {debug_path:?}: unexpected offset..", ); continue; }; @@ -175,56 +159,40 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let mut slot_buf = vec![0u8; 32 - 8]; slot_buf.extend(slot.to_be_bytes()); - header_body.push_str( - format!( - "#define STORAGE_SLOT_{} {} // {}\n", - &label, - c_bytearray_initializer(&slot_buf), - val_type - ) - .as_str(), + header_body += &format!( + "#define STORAGE_SLOT_{label} {} // {val_type}\n", + c_bytearray_initializer(&slot_buf), ); if val_type.starts_with("t_array(") { if val_type.ends_with(")dyn_storage") { let mut keccak = Keccak::v256(); keccak.update(&slot_buf); keccak.finalize(&mut slot_buf); - header_body.push_str( - format!( - "#define STORAGE_BASE_{} {} // {}\n", - &label, - c_bytearray_initializer(&slot_buf), - val_type - ) - .as_str(), + header_body += &format!( + "#define STORAGE_BASE_{label} {} // {val_type}\n", + c_bytearray_initializer(&slot_buf), ); } } else if !val_type.starts_with("t_mapping") { - header_body.push_str( - format!( - "#define STORAGE_END_OFFSET_{} {} // {}\n", - &label, - offset.to_string(), - val_type - ) - .as_str(), + header_body += &format!( + "#define STORAGE_END_OFFSET_{label} {offset} // {val_type}\n", ); } } } else { - println!("skipping output for {:?}: not an array..", &debug_path); + println!("skipping output for {debug_path:?}: not an array.."); } debug_path.pop(); } else { println!("skipping output for {:?}: not an object..", &debug_path); } debug_path.pop(); - if header_body.len() != 0 { + if !header_body.is_empty() { let mut unique_identifier = String::from("__"); - unique_identifier.push_str(&solidity_file_name.to_uppercase()); - unique_identifier.push('_'); - unique_identifier.push_str(&contract_name.to_uppercase()); - unique_identifier.push('_'); + unique_identifier += &solidity_file_name.to_uppercase(); + unique_identifier += "_"; + unique_identifier += &contract_name.to_uppercase(); + unique_identifier += "_"; let contents = format!( r#" // autogenerated by cargo-stylus @@ -257,7 +225,7 @@ ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); fs::write(&pathbuf, &contents)?; pathbuf.pop(); } - if router_body.len() != 0 { + if !router_body.is_empty() { let contents = format!( r#" // autogenerated by cargo-stylus diff --git a/src/c/header.rs b/src/c/header.rs index 8e29861..f18e4e5 100644 --- a/src/c/header.rs +++ b/src/c/header.rs @@ -23,11 +23,13 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { println!("skipping output for {:?} not an object..", &debug_path); continue; }; - pathbuf.push(&solidity_file_name); + pathbuf.push(solidity_file_name); fs::create_dir_all(&pathbuf)?; + for (contract_name, contract_val) in contracts.iter() { let mut debug_path = debug_path.clone(); - debug_path.push(&contract_name); + debug_path.push(contract_name); + let Some(properties) = contract_val.as_object() else { println!("skipping output for {:?} not an object..", &debug_path); continue; @@ -55,7 +57,8 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let mut router_body = String::default(); for (simple_name, mut overloads) in methods { - overloads.sort_by(|a, b| a.signature().cmp(&b.signature())); + overloads.sort_by_key(|a| a.signature()); + for (index, overload) in overloads.iter().enumerate() { let c_name = match index { 0 => simple_name.clone(), @@ -82,38 +85,23 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { true, ), }; - header_body.push_str( - format!( - "#define SELECTOR_{} 0x{:08x} // {}\n", - c_name, - selector, - overload.signature() - ) - .as_str(), + header_body += &format!( + "#define SELECTOR_{c_name} 0x{selector:08x} // {}\n", + overload.signature() ); - header_body.push_str( - format!( - "ArbResult {}{}; // {}\n", - c_name, - hdr_params, - overload.signature() - ) - .as_str(), + header_body += &format!( + "ArbResult {c_name}{hdr_params}; // {}\n", + overload.signature() ); - router_body - .push_str(format!(" if (selector==SELECTOR_{}) {{\n", c_name,).as_str()); + router_body += &format!(" if (selector==SELECTOR_{c_name}) {{\n"); if !payable { - router_body.push_str( - format!(" if (!bebi32_is_0(value)) revert();\n",).as_str(), - ); + router_body += " if (!bebi32_is_0(value)) revert();\n"; } - router_body.push_str( - format!(" return {}{};\n }}\n", c_name, call_params).as_str(), - ); + router_body += &format!(" return {c_name}{call_params};\n }}\n"); } } - if header_body.len() != 0 { + if !header_body.is_empty() { header_body.push('\n'); } debug_path.push("storageLayout"); @@ -157,18 +145,18 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { .map(|input| -> String { format!("0x{:02x}", input) }) .collect(); let slot_vals = slot_strings.join(", "); - header_body.push_str("#define STORAGE_SLOT_"); - header_body.push_str(&label); - header_body.push_str(" {"); - header_body.push_str(slot_vals.as_str()); - header_body.push_str("} // "); - header_body.push_str(val_type); - header_body.push('\n'); - header_body.push_str("#define STORAGE_OFFSET_"); - header_body.push_str(&label); - header_body.push(' '); - header_body.push_str(offset.to_string().as_str()); - header_body.push('\n'); + header_body += "#define STORAGE_SLOT_"; + header_body += label; + header_body += " {"; + header_body += &slot_vals; + header_body += "} // "; + header_body += val_type; + header_body += "\n"; + header_body += "#define STORAGE_OFFSET_"; + header_body += label; + header_body += " "; + header_body += &offset.to_string(); + header_body += "\n"; } } else { println!("skipping output for {:?}: not an array..", &debug_path); @@ -178,12 +166,12 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { println!("skipping output for {:?}: not an object..", &debug_path); } debug_path.pop(); - if header_body.len() != 0 { + if !header_body.is_empty() { let mut unique_identifier = String::from("__"); - unique_identifier.push_str(&solidity_file_name.to_uppercase()); - unique_identifier.push('_'); - unique_identifier.push_str(&contract_name.to_uppercase()); - unique_identifier.push('_'); + unique_identifier += &solidity_file_name.to_uppercase(); + unique_identifier += "_"; + unique_identifier += &contract_name.to_uppercase(); + unique_identifier += "_"; let contents = format!( r#" // autogenerated by cargo-stylus @@ -216,7 +204,7 @@ ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); fs::write(&pathbuf, &contents)?; pathbuf.pop(); } - if router_body.len() != 0 { + if !router_body.is_empty() { let contents = format!( r#" // autogenerated by cargo-stylus From 5b1b90545ebc1872d212740469e665da771b0303 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 17 Sep 2023 13:50:03 -0600 Subject: [PATCH 15/17] use writeln --- src/c/gen.rs | 92 ++++++++++++++++++++++++++----------------------- src/c/header.rs | 3 ++ 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/c/gen.rs b/src/c/gen.rs index 2d7adee..564d952 100644 --- a/src/c/gen.rs +++ b/src/c/gen.rs @@ -1,17 +1,17 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md + use alloy_json_abi::{Function, JsonAbi, StateMutability}; use eyre::bail; use serde_json::Value; -use std::collections::HashMap; use std::fs; use std::io::BufReader; +use std::{collections::HashMap, fmt::Write}; use tiny_keccak::{Hasher, Keccak}; fn c_bytearray_initializer(val: &[u8]) -> String { - let slot_strings: Vec = val - .iter() - .map(|input| -> String { format!("0x{:02x}", input) }) - .collect(); - format!("{{{}}}", slot_strings.join(", ")) + let inner: Vec<_> = val.iter().map(|byte| format!("0x{byte:02x}")).collect(); + format!("{{{}}}", inner.join(", ")) } pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { @@ -25,7 +25,8 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let mut pathbuf = std::path::PathBuf::new(); pathbuf.push(out_path); - for (solidity_file_name, solidity_file_out) in input_contracts.iter() { + + for (solidity_file_name, solidity_file_out) in input_contracts { let debug_path = vec![solidity_file_name.as_str()]; let Some(contracts) = solidity_file_out.as_object() else { println!("skipping output for {:?} not an object..", &debug_path); @@ -34,7 +35,7 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { pathbuf.push(solidity_file_name); fs::create_dir_all(&pathbuf)?; - for (contract_name, contract_val) in contracts.iter() { + for (contract_name, contract_val) in contracts { let mut debug_path = debug_path.clone(); debug_path.push(contract_name); @@ -52,17 +53,14 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let abi: JsonAbi = serde_json::from_str(&abi_json)?; for function in abi.functions() { let name = function.name.clone(); - methods - .entry(name) - .or_insert(Vec::default()) - .push(function.clone()); + methods.entry(name).or_default().push(function.clone()); } } else { println!("skipping abi for {:?}: not found", &debug_path); } - let mut header_body = String::default(); - let mut router_body = String::default(); + let mut header = String::default(); + let mut router = String::default(); for (simple_name, mut overloads) in methods { overloads.sort_by_key(|a| a.signature()); @@ -70,9 +68,10 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { for (index, overload) in overloads.iter().enumerate() { let c_name = match index { 0 => simple_name.clone(), - x => format!("{}_{}", simple_name, x), + x => format!("{simple_name}_{x}"), }; let selector = u32::from_be_bytes(overload.selector()); + let (hdr_params, call_params, payable) = match overload.state_mutability { StateMutability::Pure => { ("(uint8_t *input, size_t len)", "(input, len)", false) @@ -95,48 +94,52 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { }; let sig = &overload.signature(); + writeln!( + header, + "#define SELECTOR_{c_name} 0x{selector:08x} // {sig}" + )?; + writeln!(header, "ArbResult {c_name}{hdr_params}; // {sig}")?; - header_body += - &format!("#define SELECTOR_{c_name} 0x{selector:08x} // {sig}\n"); - header_body += &format!("ArbResult {c_name}{hdr_params}; // {sig}\n"); - router_body += &format!(" if (selector==SELECTOR_{c_name}) {{\n"); + writeln!(router, " if (selector==SELECTOR_{c_name}) {{")?; if !payable { - router_body += " if (!bebi32_is_0(value)) revert();\n"; + writeln!(router, " if (!bebi32_is_0(value)) revert();")?; } - router_body += &format!(" return {c_name}{call_params};\n }}\n"); + writeln!(router, " return {c_name}{call_params};\n }}")?; } } - if !header_body.is_empty() { - header_body.push('\n'); + if !header.is_empty() { + header.push('\n'); } debug_path.push("storageLayout"); + if let Some(Value::Object(layout_vals)) = properties.get("storageLayout") { debug_path.push("storage"); + if let Some(Value::Array(storage_arr)) = layout_vals.get("storage") { - for storage_val in storage_arr.iter() { + for storage_val in storage_arr { let Some(storage_obj) = storage_val.as_object() else { - println!("skipping output inside {:?}: not an object..", &debug_path); + println!("skipping output inside {debug_path:?}: not an object.."); continue; }; let Some(Value::String(label)) = storage_obj.get("label") else { - println!("skipping output inside {:?}: no label..", &debug_path); + println!("skipping output inside {debug_path:?}: no label.."); continue; }; let Some(Value::String(slot)) = storage_obj.get("slot") else { - println!("skipping output inside {:?}: no slot..", &debug_path); + println!("skipping output inside {debug_path:?}: no slot.."); continue; }; let Ok(slot) = slot.parse::() else { - println!("skipping output inside {:?}: slot not u64 ..", &debug_path); + println!("skipping output inside {debug_path:?}: slot not u64.."); continue; }; let Some(Value::String(val_type)) = storage_obj.get("type") else { - println!("skipping output inside {:?}: no type..", &debug_path); + println!("skipping output inside {debug_path:?}: no type.."); continue; }; let Some(Value::Number(read_offset)) = storage_obj.get("offset") else { - println!("skipping output inside {:?}: no offset..", &debug_path); + println!("skipping output inside {debug_path:?}: no offset.."); continue; }; let offset = match read_offset.as_i64() { @@ -159,24 +162,27 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { let mut slot_buf = vec![0u8; 32 - 8]; slot_buf.extend(slot.to_be_bytes()); - header_body += &format!( - "#define STORAGE_SLOT_{label} {} // {val_type}\n", + writeln!( + header, + "#define STORAGE_SLOT_{label} {} // {val_type}", c_bytearray_initializer(&slot_buf), - ); + )?; if val_type.starts_with("t_array(") { if val_type.ends_with(")dyn_storage") { let mut keccak = Keccak::v256(); keccak.update(&slot_buf); keccak.finalize(&mut slot_buf); - header_body += &format!( - "#define STORAGE_BASE_{label} {} // {val_type}\n", + writeln!( + header, + "#define STORAGE_BASE_{label} {} // {val_type}", c_bytearray_initializer(&slot_buf), - ); + )?; } } else if !val_type.starts_with("t_mapping") { - header_body += &format!( - "#define STORAGE_END_OFFSET_{label} {offset} // {val_type}\n", - ); + writeln!( + header, + "#define STORAGE_END_OFFSET_{label} {offset} // {val_type}", + )?; } } } else { @@ -187,7 +193,7 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { println!("skipping output for {:?}: not an object..", &debug_path); } debug_path.pop(); - if !header_body.is_empty() { + if !header.is_empty() { let mut unique_identifier = String::from("__"); unique_identifier += &solidity_file_name.to_uppercase(); unique_identifier += "_"; @@ -217,7 +223,7 @@ ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); #endif // {uniq} "#, uniq = unique_identifier, - body = header_body + body = header ); let filename: String = contract_name.into(); @@ -225,7 +231,7 @@ ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); fs::write(&pathbuf, &contents)?; pathbuf.pop(); } - if !router_body.is_empty() { + if !router.is_empty() { let contents = format!( r#" // autogenerated by cargo-stylus @@ -253,7 +259,7 @@ ArbResult {contract}_entry(uint8_t *input, size_t len) {{ ENTRYPOINT({contract}_entry) "#, contract = contract_name, - body = router_body + body = router ); let filename: String = contract_name.into(); diff --git a/src/c/header.rs b/src/c/header.rs index f18e4e5..6cfa2af 100644 --- a/src/c/header.rs +++ b/src/c/header.rs @@ -1,3 +1,6 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md + use alloy_json_abi::{Function, JsonAbi, StateMutability}; use eyre::bail; use serde_json::Value; From b7ac0c40bc176f91c526cfd035e6d3541e5b4d81 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 17 Sep 2023 13:51:07 -0600 Subject: [PATCH 16/17] rm duplicate code --- src/c/header.rs | 250 ------------------------------------------------ src/c/mod.rs | 1 - 2 files changed, 251 deletions(-) delete mode 100644 src/c/header.rs diff --git a/src/c/header.rs b/src/c/header.rs deleted file mode 100644 index 6cfa2af..0000000 --- a/src/c/header.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2023, Offchain Labs, Inc. -// For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md - -use alloy_json_abi::{Function, JsonAbi, StateMutability}; -use eyre::bail; -use serde_json::Value; -use std::collections::HashMap; -use std::fs; -use std::io::BufReader; -use tiny_keccak::{Hasher, Keccak}; - -pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { - let f = fs::File::open(&in_path)?; - - let input: Value = serde_json::from_reader(BufReader::new(f))?; - - let Some(input_contracts) = input["contracts"].as_object() else { - bail!("did not find top-level contracts object in {}", in_path) - }; - - let mut pathbuf = std::path::PathBuf::new(); - pathbuf.push(out_path); - for (solidity_file_name, solidity_file_out) in input_contracts.iter() { - let debug_path = vec![solidity_file_name.as_str()]; - let Some(contracts) = solidity_file_out.as_object() else { - println!("skipping output for {:?} not an object..", &debug_path); - continue; - }; - pathbuf.push(solidity_file_name); - fs::create_dir_all(&pathbuf)?; - - for (contract_name, contract_val) in contracts.iter() { - let mut debug_path = debug_path.clone(); - debug_path.push(contract_name); - - let Some(properties) = contract_val.as_object() else { - println!("skipping output for {:?} not an object..", &debug_path); - continue; - }; - - let mut methods: HashMap> = HashMap::default(); - - if let Some(raw) = properties.get("abi") { - // Sadly, JsonAbi = serde_json::from_value is not supported. - // Tonight, we hack! - let abi_json = serde_json::to_string(raw)?; - let abi: JsonAbi = serde_json::from_str(&abi_json)?; - for function in abi.functions() { - let name = function.name.clone(); - methods - .entry(name) - .or_insert(Vec::default()) - .push(function.clone()); - } - } else { - println!("skipping abi for {:?}: not found", &debug_path); - } - - let mut header_body = String::default(); - let mut router_body = String::default(); - - for (simple_name, mut overloads) in methods { - overloads.sort_by_key(|a| a.signature()); - - for (index, overload) in overloads.iter().enumerate() { - let c_name = match index { - 0 => simple_name.clone(), - x => format!("{}_{}", simple_name, x), - }; - let selector = u32::from_be_bytes(overload.selector()); - let (hdr_params, call_params, payable) = match overload.state_mutability { - StateMutability::Pure => { - ("(uint8_t *input, size_t len)", "(input, len)", false) - } - StateMutability::View => ( - "(const void *storage, uint8_t *input, size_t len)", - "(NULL, input, len)", - false, - ), - StateMutability::NonPayable => ( - "(void *storage, uint8_t *input, size_t len)", - "(NULL, input, len)", - false, - ), - StateMutability::Payable => ( - "(void *storage, uint8_t *input, size_t len, bebi32 value)", - "(NULL, input, len, value)", - true, - ), - }; - header_body += &format!( - "#define SELECTOR_{c_name} 0x{selector:08x} // {}\n", - overload.signature() - ); - header_body += &format!( - "ArbResult {c_name}{hdr_params}; // {}\n", - overload.signature() - ); - router_body += &format!(" if (selector==SELECTOR_{c_name}) {{\n"); - if !payable { - router_body += " if (!bebi32_is_0(value)) revert();\n"; - } - router_body += &format!(" return {c_name}{call_params};\n }}\n"); - } - } - - if !header_body.is_empty() { - header_body.push('\n'); - } - debug_path.push("storageLayout"); - if let Some(Value::Object(layout_vals)) = properties.get("storageLayout") { - debug_path.push("storage"); - if let Some(Value::Array(storage_arr)) = layout_vals.get("storage") { - for storage_val in storage_arr.iter() { - let Some(storage_obj) = storage_val.as_object() else { - println!("skipping output inside {:?}: not an object..", &debug_path); - continue; - }; - let Some(Value::String(label)) = storage_obj.get("label") else { - println!("skipping output inside {:?}: no label..", &debug_path); - continue; - }; - let Some(Value::String(slot)) = storage_obj.get("slot") else { - println!("skipping output inside {:?}: no slot..", &debug_path); - continue; - }; - let Ok(slot) = slot.parse::() else { - println!("skipping output inside {:?}: slot not u64 ..", &debug_path); - continue; - }; - let Some(Value::String(val_type)) = storage_obj.get("type") else { - println!("skipping output inside {:?}: no type..", &debug_path); - continue; - }; - let Some(Value::Number(offset)) = storage_obj.get("offset") else { - println!("skipping output inside {:?}: no offset..", &debug_path); - continue; - }; - let mut slot_buf = vec![0u8; 32 - 8]; - slot_buf.extend(slot.to_be_bytes()); - if val_type.starts_with("t_array(") && val_type.ends_with(")dyn_storage") { - let mut keccak = Keccak::v256(); - keccak.update(&slot_buf); - keccak.finalize(&mut slot_buf); - } - let slot_strings: Vec = slot_buf - .iter() - .map(|input| -> String { format!("0x{:02x}", input) }) - .collect(); - let slot_vals = slot_strings.join(", "); - header_body += "#define STORAGE_SLOT_"; - header_body += label; - header_body += " {"; - header_body += &slot_vals; - header_body += "} // "; - header_body += val_type; - header_body += "\n"; - header_body += "#define STORAGE_OFFSET_"; - header_body += label; - header_body += " "; - header_body += &offset.to_string(); - header_body += "\n"; - } - } else { - println!("skipping output for {:?}: not an array..", &debug_path); - } - debug_path.pop(); - } else { - println!("skipping output for {:?}: not an object..", &debug_path); - } - debug_path.pop(); - if !header_body.is_empty() { - let mut unique_identifier = String::from("__"); - unique_identifier += &solidity_file_name.to_uppercase(); - unique_identifier += "_"; - unique_identifier += &contract_name.to_uppercase(); - unique_identifier += "_"; - - let contents = format!( - r#" // autogenerated by cargo-stylus -#ifndef {uniq} -#define {uniq} - -#include -#include - -#ifdef __cplusplus -extern "C" {{ -#endif - -ArbResult default_func(void *storage, uint8_t *input, size_t len, bebi32 value); - -{body} - -#ifdef __cplusplus -}} -#endif - -#endif // {uniq} -"#, - uniq = unique_identifier, - body = header_body - ); - - let filename: String = contract_name.into(); - pathbuf.push(filename + ".h"); - fs::write(&pathbuf, &contents)?; - pathbuf.pop(); - } - if !router_body.is_empty() { - let contents = format!( - r#" // autogenerated by cargo-stylus - -#include "{contract}.h" -#include -#include -#include - - -ArbResult {contract}_entry(uint8_t *input, size_t len) {{ - bebi32 value; - msg_value(value); - if (len < 4) {{ - return default_func(NULL, input, len, value); - }} - uint32_t selector = bebi_get_u32(input, 0); - input +=4; - len -=4; -{body} - input -=4; - len +=4; - return default_func(NULL, input, len, value); -}} - -ENTRYPOINT({contract}_entry) -"#, - contract = contract_name, - body = router_body - ); - - let filename: String = contract_name.into(); - pathbuf.push(filename + "_main.c"); - fs::write(&pathbuf, &contents)?; - pathbuf.pop(); - } - } - pathbuf.pop(); - } - Ok(()) -} diff --git a/src/c/mod.rs b/src/c/mod.rs index 38c0601..dd261c3 100644 --- a/src/c/mod.rs +++ b/src/c/mod.rs @@ -2,4 +2,3 @@ // For licensing, see https://github.com/OffchainLabs/cargo-stylus/blob/main/licenses/COPYRIGHT.md pub mod gen; -pub mod header; From 840dfe3f92f010a2d503dc06d9c890aaae32bd6a Mon Sep 17 00:00:00 2001 From: Tsahi Zidenberg Date: Mon, 18 Sep 2023 15:00:34 -0600 Subject: [PATCH 17/17] c-gen: renamed is_0 to is_zero --- src/c/gen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c/gen.rs b/src/c/gen.rs index 564d952..dc98f4e 100644 --- a/src/c/gen.rs +++ b/src/c/gen.rs @@ -102,7 +102,7 @@ pub fn c_gen(in_path: String, out_path: String) -> eyre::Result<()> { writeln!(router, " if (selector==SELECTOR_{c_name}) {{")?; if !payable { - writeln!(router, " if (!bebi32_is_0(value)) revert();")?; + writeln!(router, " if (!bebi32_is_zero(value)) revert();")?; } writeln!(router, " return {c_name}{call_params};\n }}")?; }