├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── example ├── Cargo.toml ├── build.rs ├── contracts │ └── test.sol └── src │ └── lib.rs └── src ├── error.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | Cargo.lock 4 | example/contracts/*.abi 5 | example/contracts/*.bin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | # need sudo and trusty to use `snap` 6 | sudo: required 7 | dist: trusty 8 | language: rust 9 | cache: cargo 10 | 11 | matrix: 12 | include: 13 | # `solc` test (`solcjs` test is the next item in this list) 14 | - rust: stable 15 | install: 16 | - sudo apt-get --yes install snapd 17 | - sudo snap install solc --stable 18 | - snap list 19 | before_script: 20 | - export PATH=/snap/bin:${PATH} 21 | - rustup component add rustfmt-preview 22 | script: 23 | - cargo fmt --all -- --check 24 | - cd example 25 | - cargo build 26 | # after success build rust documentation and push it to gh-pages branch 27 | after_success: | 28 | cargo doc --no-deps && \ 29 | sudo pip install ghp-import && \ 30 | ghp-import -n target/doc && \ 31 | git push -qf https://${GITHUB_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages 32 | # `solcjs` test 33 | - rust: stable 34 | install: 35 | - sudo apt-get --yes install snapd 36 | - sudo snap install node --classic --channel=9/stable 37 | - snap list 38 | - npm install -g solc 39 | before_script: 40 | - export PATH=/snap/bin:${PATH} 41 | script: 42 | - cd example 43 | - cargo build 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solc" 3 | version = "0.1.0" 4 | authors = ["Maximilian Krüger "] 5 | 6 | [dependencies] 7 | error-chain = "0.12.0" 8 | regex = "1.0.0" 9 | lazy_static = "1.0.1" 10 | 11 | [workspace] 12 | members = [ 13 | "example", 14 | ] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-solc 2 | 3 | *this is a work in progress. it works as described in the example. the API might change frequently in the future* 4 | 5 | [![Build Status][travis-image]][travis-url] 6 | 7 | [travis-image]: https://travis-ci.org/paritytech/rust_solc.svg?branch=master 8 | [travis-url]: https://travis-ci.org/paritytech/rust_solc 9 | 10 | **easily compile solidity files from rust** 11 | 12 | shells out to `solc` or `solcjs` (whichever is available in that order). 13 | 14 | either `solc` (C++) or `solcjs` (JS) must be installed and in `$PATH`. 15 | [click here to see how to install `solc`](https://solidity.readthedocs.io/en/latest/installing-solidity.html#binary-packages) 16 | [click here to see how to install `solcjs`](https://solidity.readthedocs.io/en/latest/installing-solidity.html#npm-node-js) 17 | 18 | ```rust 19 | extern crate solc; 20 | 21 | fn main() { 22 | let input_directory = "./contracts"; 23 | let output_directory = "./contracts"; 24 | 25 | // first tries solc 26 | // then tries solcjs 27 | // returns error if no compiler available 28 | solc::compile_dir(&input_directory, &output_directory).unwrap(); 29 | 30 | // now `./contracts` contains a `*.bin` and a `*.abi` file 31 | // for every contract found in `*.sol` file in `./contracts` 32 | } 33 | ``` 34 | 35 | this is an early version that likely misses features. 36 | [open an issue if you're missing something](https://github.com/snd/rust_solc/issues/new) 37 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | authors = ["Maximilian Krüger "] 5 | 6 | [dependencies] 7 | solc = { path = ".." } 8 | ethabi = { git = "https://github.com/paritytech/ethabi" } 9 | ethabi-derive = { git = "https://github.com/paritytech/ethabi" } 10 | ethabi-contract = { git = "https://github.com/paritytech/ethabi" } 11 | serde_json = "1.0.20" 12 | 13 | [build-dependencies] 14 | solc = { path = ".." } 15 | -------------------------------------------------------------------------------- /example/build.rs: -------------------------------------------------------------------------------- 1 | extern crate solc; 2 | 3 | fn main() { 4 | // always rerun build script if contract has changed 5 | println!("cargo:rerun-if-changed=./contracts/test.sol"); 6 | 7 | solc::compile_dir("./contracts", "./contracts").unwrap(); 8 | } 9 | -------------------------------------------------------------------------------- /example/contracts/test.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.1; 2 | 3 | contract GetSenderTest { 4 | function getSender() public returns (address) { 5 | return msg.sender; 6 | } 7 | } 8 | 9 | contract GetValueTest { 10 | function getValue() payable public returns (uint) { 11 | return msg.value; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate ethabi; 2 | #[macro_use] 3 | extern crate ethabi_derive; 4 | #[macro_use] 5 | extern crate ethabi_contract; 6 | 7 | use_contract!(sender_test, "./contracts/GetSenderTest.abi"); 8 | use_contract!(value_test, "./contracts/GetValueTest.abi"); 9 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::process::ExitStatus; 3 | 4 | error_chain! { 5 | types { 6 | Error, ErrorKind, ResultExt, Result; 7 | } 8 | 9 | foreign_links { 10 | Io(io::Error); 11 | } 12 | 13 | errors { 14 | ExitStatusNotSuccess( 15 | command: String, 16 | exit_status: ExitStatus, 17 | stdout: String, 18 | stderr: String 19 | ) { 20 | description("command exit status is not success (0)"), 21 | display("command (`{}`) is not success (0) but `{}`", command, exit_status) 22 | } 23 | NoSolidityCompilerFound { 24 | description("neither `solc` nor `solcjs` are in path"), 25 | display("neither `solc` nor `solcjs` are in path. please install either `solc` or `solcjs` via https://solidity.readthedocs.io/en/latest/installing-solidity.html") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate error_chain; 3 | 4 | #[macro_use] 5 | extern crate lazy_static; 6 | 7 | use std::io::Write; 8 | use std::path::{Path, PathBuf}; 9 | use std::process::{Command, Output, Stdio}; 10 | 11 | extern crate regex; 12 | use regex::Regex; 13 | 14 | pub mod error; 15 | use error::ResultExt; 16 | 17 | /// returns whether `solc` is in path. 18 | /// 19 | /// `solc` is the C++ implementation of the solidity compiler. 20 | pub fn is_solc_available() -> bool { 21 | solc_version().is_ok() 22 | } 23 | 24 | /// returns whether `solcjs` is in path. 25 | /// 26 | /// `solcjs` is the javascript implementation of the solidity compiler. 27 | pub fn is_solcjs_available() -> bool { 28 | solcjs_version().is_ok() 29 | } 30 | 31 | /// returns the output of `solc --version`. 32 | /// 33 | /// more specifically returns the last output line which is the version string. 34 | /// `solc` is the C++ implementation of the solidity compiler. 35 | pub fn solc_version() -> error::Result { 36 | common_version("solc") 37 | } 38 | 39 | /// returns the output of `solcjs --version`. 40 | /// 41 | /// more specifically returns the last output line which is the version string. 42 | /// `solcjs` is the javascript implementation of the solidity compiler. 43 | pub fn solcjs_version() -> error::Result { 44 | common_version("solcjs") 45 | } 46 | 47 | /// version code that's common for `solc` and `solcjs` 48 | fn common_version(command_name: &str) -> error::Result { 49 | let command_output = Command::new(command_name) 50 | .arg("--version") 51 | .output() 52 | .chain_err(|| format!("failed to run `{} --version`", command_name))?; 53 | if !command_output.status.success() { 54 | return Err(exit_status(command_name, command_output).into()); 55 | } 56 | let stdout = String::from_utf8(command_output.stdout) 57 | .chain_err(|| format!("output from `{} --version` is not utf8", command_name))?; 58 | let version = stdout 59 | .lines() 60 | .last() 61 | .chain_err(|| format!("output from `{} --version` is empty", command_name))? 62 | .to_owned(); 63 | Ok(version) 64 | } 65 | 66 | /// shells out to either `solc` or `solcjs` (whichever is available in that order) 67 | /// to compile all solidity files in `input_dir_path` 68 | /// into abi and bin files in `output_dir_path`. 69 | pub fn compile_dir, B: AsRef>( 70 | input_dir_path: A, 71 | output_dir_path: B, 72 | ) -> error::Result<()> { 73 | let is_solc_available = is_solc_available(); 74 | 75 | if !is_solc_available && !is_solcjs_available() { 76 | return Err(error::ErrorKind::NoSolidityCompilerFound.into()); 77 | } 78 | 79 | for input_file_path in solidity_file_paths(input_dir_path)? { 80 | if is_solc_available { 81 | solc_compile(&input_file_path, &output_dir_path)?; 82 | } else { 83 | solcjs_compile(&input_file_path, &output_dir_path)?; 84 | rename_solcjs_outputs(&input_file_path, &output_dir_path)?; 85 | } 86 | } 87 | Ok(()) 88 | } 89 | 90 | /// shells out to `solc` to compile the single file at 91 | /// `input_file_path` into abi and bin files in `output_dir_path`. 92 | /// 93 | /// `solc` is the C++ implementation of the solidity compiler. 94 | pub fn solc_compile, B: AsRef>( 95 | input_file_path: A, 96 | output_dir_path: B, 97 | ) -> error::Result { 98 | let command_output = Command::new("solc") 99 | .arg("--bin") 100 | .arg("--abi") 101 | .arg("--overwrite") 102 | .arg("--optimize") 103 | .arg("--output-dir") 104 | .arg(output_dir_path.as_ref()) 105 | .arg(input_file_path.as_ref()) 106 | .output() 107 | .chain_err(|| "failed to run process `solc`")?; 108 | 109 | if !command_output.status.success() { 110 | return Err(exit_status("solc", command_output).into()); 111 | } 112 | 113 | Ok(command_output) 114 | } 115 | 116 | /// shells out to `solcjs` to compile the single file at 117 | /// `input_file_path` into abi and bin files in `output_dir_path`. 118 | /// 119 | /// `solcjs` is the javascript implementation of the solidity compiler. 120 | pub fn solcjs_compile, B: AsRef>( 121 | input_file_path: A, 122 | output_dir_path: B, 123 | ) -> error::Result { 124 | let command_output = Command::new("solcjs") 125 | .arg("--bin") 126 | .arg("--abi") 127 | .arg("--overwrite") 128 | .arg("--optimize") 129 | .arg("--output-dir") 130 | .arg(output_dir_path.as_ref()) 131 | .arg(input_file_path.as_ref()) 132 | .output() 133 | .chain_err(|| "failed to run process `solcjs`")?; 134 | 135 | if !command_output.status.success() { 136 | return Err(exit_status("solcjs", command_output).into()); 137 | } 138 | 139 | Ok(command_output) 140 | } 141 | 142 | /// returns all solidity files in `directory` 143 | pub fn solidity_file_paths>(directory: T) -> std::io::Result> { 144 | let mut results = Vec::new(); 145 | 146 | for maybe_entry in std::fs::read_dir(directory)? { 147 | let path = maybe_entry?.path(); 148 | if let Some(extension) = path.extension().map(|x| x.to_os_string()) { 149 | if extension.as_os_str() == "sol" { 150 | results.push(path); 151 | } 152 | } 153 | } 154 | 155 | Ok(results) 156 | } 157 | 158 | pub fn input_file_path_to_solcjs_output_name_prefix>( 159 | input_file_path: A, 160 | ) -> error::Result { 161 | lazy_static! { 162 | static ref RE: Regex = Regex::new(r"[:./]").unwrap(); 163 | } 164 | 165 | Ok(format!( 166 | "{}_", 167 | RE.replace_all( 168 | input_file_path.as_ref().to_str().ok_or(format!( 169 | "input file path `{:?}` must be utf8 but isn't", 170 | input_file_path.as_ref() 171 | ))?, 172 | "_" 173 | ) 174 | )) 175 | } 176 | 177 | /// `solcjs` prefixes output files (one per contract) with the input filename while `solc` does not. 178 | /// rename files so output files can be found in identical places regardless 179 | /// of whether `solcjs` or `solc` is used 180 | /// 181 | /// effectively undoes the following line in `solcjs` 182 | /// https://github.com/ethereum/solc-js/blob/43e8fe080686fb9627ee9ff93959e3aa61496d22/solcjs#L117 183 | pub fn rename_solcjs_outputs, B: AsRef>( 184 | input_file_path: A, 185 | output_dir_path: B, 186 | ) -> error::Result<()> { 187 | let prefix = input_file_path_to_solcjs_output_name_prefix(&input_file_path)?; 188 | 189 | for maybe_entry in std::fs::read_dir(&output_dir_path)? { 190 | let src_path = maybe_entry?.path(); 191 | if let Some(file_name) = src_path.file_name().map(|x| x.to_os_string()) { 192 | let file_name = file_name.to_str().ok_or(format!( 193 | "file name `{:?}` in dir `{:?}` must be utf8 but isn't", 194 | file_name, 195 | output_dir_path.as_ref() 196 | ))?; 197 | if !file_name.starts_with(&prefix) { 198 | continue; 199 | } 200 | 201 | if let Some(extension) = src_path.extension() { 202 | if extension != "abi" && extension != "bin" { 203 | continue; 204 | } 205 | } 206 | 207 | // dst = src path with `prefix` stripped from front of file name 208 | let dst_path = src_path.with_file_name(&file_name[prefix.len()..]); 209 | std::fs::rename(src_path, dst_path)?; 210 | } 211 | } 212 | Ok(()) 213 | } 214 | 215 | /// at the time of writing this is broken for `solcjs` 216 | /// due to issue https://github.com/ethereum/solc-js/issues/126 217 | pub fn standard_json(input_json: &str) -> error::Result { 218 | let is_solc_available = is_solc_available(); 219 | 220 | if !is_solc_available && !is_solcjs_available() { 221 | return Err(error::ErrorKind::NoSolidityCompilerFound.into()); 222 | } 223 | 224 | let command_name = if is_solc_available { "solc" } else { "solcjs" }; 225 | 226 | common_standard_json(command_name, input_json) 227 | } 228 | 229 | fn common_standard_json(command_name: &str, input_json: &str) -> error::Result { 230 | let full_command = format!("{} --standard-json", command_name); 231 | 232 | let mut process = Command::new(command_name) 233 | .arg("--standard-json") 234 | .stdin(Stdio::piped()) 235 | .stdout(Stdio::piped()) 236 | .spawn() 237 | .chain_err(|| format!("failed to spawn process `{}`", &full_command))?; 238 | 239 | { 240 | let stdin = process 241 | .stdin 242 | .as_mut() 243 | .chain_err(|| format!("failed to open stdin for process `{}`", &full_command))?; 244 | 245 | stdin.write_all(input_json.as_bytes()).chain_err(|| { 246 | format!( 247 | "failed to write input json to stdin for process `{}`", 248 | &full_command 249 | ) 250 | })?; 251 | } 252 | 253 | let output = process.wait_with_output().chain_err(|| { 254 | format!( 255 | "failed to read output json from stdout for process `{}`", 256 | &full_command 257 | ) 258 | })?; 259 | 260 | if !output.status.success() { 261 | return Err(exit_status(full_command, output).into()); 262 | } 263 | 264 | let output_json = String::from_utf8(output.stdout).chain_err(|| { 265 | format!( 266 | "stdout from process `{}` must be utf8 but isn't", 267 | full_command 268 | ) 269 | })?; 270 | 271 | Ok(output_json) 272 | } 273 | 274 | fn exit_status>(command: T, output: Output) -> error::ErrorKind { 275 | let to_str = |d: Vec| String::from_utf8(d).unwrap_or_else(|_| "".into()); 276 | error::ErrorKind::ExitStatusNotSuccess( 277 | command.into(), 278 | output.status, 279 | to_str(output.stdout), 280 | to_str(output.stderr), 281 | ) 282 | } 283 | --------------------------------------------------------------------------------