├── .gdb_history ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── esvm ├── Cargo.toml ├── benches │ └── contracts.rs ├── bin │ ├── scheduler.rs │ └── service.rs ├── contracts │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── src │ ├── bytecode.rs │ ├── disasm.rs │ ├── lib.rs │ ├── main.rs │ ├── se │ │ ├── config.rs │ │ ├── env.rs │ │ ├── expr │ │ │ ├── boolector.rs │ │ │ ├── bval.rs │ │ │ ├── formel_builder.rs │ │ │ ├── mod.rs │ │ │ ├── solver.rs │ │ │ ├── symbolic_memory.rs │ │ │ ├── yice.rs │ │ │ └── z3.rs │ │ ├── mod.rs │ │ ├── symbolic_analysis.rs │ │ ├── symbolic_edge.rs │ │ ├── symbolic_executor │ │ │ ├── call_ops.rs │ │ │ ├── executor.rs │ │ │ ├── memory_ops.rs │ │ │ ├── mod.rs │ │ │ └── stack_ops.rs │ │ ├── symbolic_graph.rs │ │ └── symbolic_state.rs │ └── test_helpers.rs └── tests │ └── integration_test.rs ├── evmexec ├── Cargo.toml ├── LICENSE ├── README.md ├── ethereum-newtypes │ ├── .gitignore │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── macros.rs ├── src │ ├── evm.rs │ ├── evmtrace.rs │ ├── genesis.rs │ └── lib.rs └── tests │ ├── files │ └── corrupted_geth.json │ └── multiple_transactions_test.rs ├── examples ├── control_flow_hijack │ ├── control.sol │ └── control.yml ├── parity │ ├── parity.sol │ ├── parity.yml │ └── parity_edited_down.sol ├── parity_reduced │ ├── reduced_parity.sol │ └── reduced_parity.yml ├── rubixi │ ├── rubixi.sol │ └── rubixi.yml └── unprotected_function │ ├── parity_bug_call.sol │ ├── parity_bug_call_with_arg.sol │ ├── parity_bug_suicide.sol │ ├── unprotected_call.yml │ ├── unprotected_call_args.yml │ └── unprotected_suicide.yml ├── media └── ethfant.png ├── parity_connector ├── Cargo.toml └── src │ ├── client.rs │ ├── lib.rs │ ├── macros.rs │ ├── main.rs │ └── types.rs └── run_all_test.sh /.gdb_history: -------------------------------------------------------------------------------- 1 | r 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /evmexec/target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | 6 | # Outputfiles 7 | *.dot 8 | *.smt2 9 | *.svg 10 | *.log 11 | *.html 12 | *.csv 13 | 14 | *.txt 15 | 16 | /log/ 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "esvm", 4 | "parity_connector", 5 | ] 6 | 7 | [profile.bench] 8 | debug = true 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chair for Sys­tems Se­cu­ri­ty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EthBMC: A Bounded Model Checker for Smart Contracts 2 | 3 |

4 | 5 |

6 | 7 | This is the code repository for our Usenix Security 2020 paper [EthBMC](https://www.usenix.org/system/files/sec20-frank.pdf). 8 | 9 | 10 | > The introduction of smart contracts has significantly advanced the state-of-the-art in cryptocurrencies. Smart contracts are programs who live on the blockchain, governing the flow of money. However, the promise of monetary gain has attracted miscreants, resulting in spectacular hacks which resulted in the loss of millions worth of currency. In response, several powerful static analysis tools were developed to address these problems. We surveyed eight recently proposed static analyzers for Ethereum smart contracts and found that none of them captures all relevant features of the Ethereum ecosystem. For example, we discovered that a precise memory model is missing and inter-contract analysis is only partially supported. 11 | > 12 | > Based on these insights, we present the design and implementation of, a bounded model checker based on symbolic execution which provides a precise model of the Ethereum network. We demonstrate its capabilities in a series of experiments. First, we compare against the eight aforementioned tools, showing that even relatively simple toy examples can obstruct other analyzers. Further proving that precise modeling is indispensable, we leverage ETHBmc capabilities for automatic vulnerability scanning. We perform a large-scale analysis of roughly 2.2 million accounts currently active on the blockchain and automatically generate 5,905 valid inputs which trigger a vulnerability. From these, 1,989 can destroy a contract at will (so called suicidal contracts) and the rest can be used by an adversary to arbitrarily extract money. Finally, we compare our large-scale analysis against two previous analysis runs, finding significantly more inputs (22.8%) than previous approaches. 13 | 14 | ## Requirements 15 | 16 | To compile and use EthBMC you will need: 17 | 18 | - Rust nightly (tested with version 1.44.0) 19 | - [evm](https://github.com/ethereum/go-ethereum) stand alone executable; make sure it is in your `$PATH` (originally with version 1.8.17; also tested with 1.10.4 - available in the [Geth & Tools](https://geth.ethereum.org/downloads/) bundle) 20 | - We offer support for three different smt solvers (tested with yices2 version 2.6.1. Note we recommend Yices2; see Section 6.5 in the paper): 21 | - [Z3](https://github.com/Z3Prover/z3) 22 | - [Yices2](https://github.com/SRI-CSL/yices2) 23 | - [Boolector](https://github.com/Boolector/boolector) 24 | 25 | 26 | When using the webservice-version (see esvm/bin): 27 | - Configuration is done using a [Rocket.toml](https://rocket.rs/v0.4/guide/configuration/#rockettoml) 28 | - When using the Service with timeouts the service expects a ethbmc binary in the PATH 29 | 30 | ### Binaries 31 | 32 | - ethbmc: Simple stand alone binary 33 | - ethbmc-service: Webservice which accepts accounts and returns the result as json 34 | - ethbmc-scheduler: Accepts two lists, an account list and a server list, then distributes the accounts between the backing servers running a service instance 35 | 36 | 37 | ### Building 38 | 39 | ``` 40 | cargo build --release 41 | ``` 42 | Note when analyzing big contracts you might have to increase Rusts stack size, see [here](https://stackoverflow.com/questions/29937697/how-to-set-the-thread-stack-size-during-compile-time). 43 | 44 | ### Usage 45 | ``` 46 | EthBMC 1.0.0 47 | EthBMC: A Bounded Model Checker for Smart Contracts 48 | 49 | USAGE: 50 | ethbmc [FLAGS] [OPTIONS] 51 | 52 | FLAGS: 53 | -x, --acc The input is an Ethereum account address, must be used with parity backend, mainnet only 54 | --concrete-copy Use concrete calldatacopy 55 | -d, --debug-grap Dump debug graph after analysis 56 | --no-optimizations Disable all optimizations 57 | --dump-solver Dump all solver queries to ./queries 58 | -h, --help Prints help information 59 | --json Output json without logging 60 | --list The input is a list of Ethereum account address, writes the result to a csv file in the 61 | output folder 62 | --no-verify Skip verification phase. 63 | -V, --version Prints version information 64 | 65 | OPTIONS: 66 | --block The blocknumber to evaluate, defaults to latest block. Note you will need 67 | an archive node for anaylzing blocks which are not the latest. 68 | -c, --call-bound Set bound for calls 69 | --cores Set the amount of cores the se can use 70 | --ip The ip of a running node. 71 | -b, --loop-bound Set bound for loops 72 | -m, --message-bound Set bound for message iteration 73 | --port The port of a running node. 74 | --solver The SMT solver to use: z3, boolector, yices2 [yices2] 75 | --solver-timeout Set solver timeout in milliseconds 76 | 77 | ARGS: 78 | Set input file / address 79 | ``` 80 | 81 | The easiest way to use EthBMC is to create an input yml file (see examples): 82 | ``` 83 | ./target/release/ethbmc examples/rubixi/rubixi.yml 84 | ``` 85 | 86 | When you have a running Ethereum node you can also use it to analyze an on-chain account: 87 | ``` 88 | ./target/release/ethbmc -x --ip ip.to.your.node 0xAccount_Address 89 | ``` 90 | 91 | When you have an archive node you can also use it to analyze an account at a specific block: 92 | ``` 93 | ./target/release/ethbmc -x --ip ip.to.your.node --block block_number_to_analyze 0xAccount_Address 94 | ``` 95 | 96 | Note when executing the parity example (examples/parity) we recommend limiting the loop execution to 1 and use concrete-copy. The bug can still be found without these restrictions, but it takes a long time. 97 | 98 | ``` 99 | ./target/release/ethbmc -b1 --concrete-copy examples/parity/parity.yml 100 | ``` 101 | 102 | #### YAML Format 103 | 104 | The yaml format allows to easily initialise a multi-account environment offline. Under state you list all the accounts which should be present in the environment. Each account gets its address as a key. Additionally you can set the balance of the accounts, the nonce and the code field. Optionally you can supply storage. These are key-value pairs which get loaded as the initial storage of an account, otherwise it is assumed empty. Additionally you must supply a victim address. This is the account from which the analysis is started. See for example the example for analysing the parity hack: 105 | 106 | ``` 107 | state: 108 | # Wallet 109 | 0xad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 110 | balance: 0x8AC7230489E80000 111 | nonce: 0x0 112 | code: 606060... 113 | storage: 114 | 0x0: 0xcafecafecafecafecafecafecafecafecafecafe # "owner" 115 | 116 | # Receiver 117 | 0xcafecafecafecafecafecafecafecafecafecafe: 118 | balance: 0x0 119 | nonce: 0x0 120 | code: 60606... 121 | 122 | victim: 0xad62f08b3b9f0ecc7251befbeff80c9bb488fe9 123 | ``` 124 | 125 | We initialise two accounts, the wallet account, which holds the stub for forwarding all requests to the library, and the Receiver account, the parity library. We additionally supply some funds to it, to simulate a hack of the wallet, as well as setting the first storage variable (0x0) (the variable holding the address of the library contract) to the second account in the environment. 126 | 127 | ### Patched Parity Node 128 | 129 | For the on-chain analysis (Section 6.2) we used a patched parity node to obtain the storage of an account (the code can be found [here](https://github.com/Joool/openethereum)). You can still use a normal node which supports the web3 API. However, the analysis then does not take storage variables into account (all variables are assumed to be zero). Note to analyze blocks at an earlier time you will need to use a patched archive node. 130 | 131 | ## Tests 132 | 133 | - Test can only be run one at a time at the moment (cargo test -- --test-threads=1) 134 | - Integration tests take a long time and should allways be run with optimizations (cargo test integra --release -- --ignored) 135 | - Some tests need a deployed parity instance, set PARITY_IP env var to the corresponding ip (PARITY_IP=127.0.0.1 cargo test parity -- --test-threads=1 --ignored) 136 | 137 | You can run all test with the supplied shell script (requires archive patched parity node): 138 | ``` 139 | PARITY_IP=127.0.0.1 ./run_all_test.sh 140 | ``` 141 | 142 | ## BibTeX 143 | 144 | When you cite our work please use the following bibtex entry: 145 | ``` 146 | @inproceedings {frank2020ethbmc, 147 | title={ETHBMC: A Bounded Model Checker for Smart Contracts}, 148 | author={Frank, Joel and Aschermann, Cornelius and Holz, Thorsten}, 149 | booktitle = {USENIX Security Symposium (USENIX Security)}, 150 | year = {2020}, 151 | } 152 | ``` 153 | 154 | ## Acknowledgement 155 | We would like to thank the [parity](https://www.parity.io/) team for open-sourcing their code. 156 | -------------------------------------------------------------------------------- /esvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esvm" 3 | version = "0.1.0" 4 | authors = [""] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rocket = "0.4.0" 9 | rocket_contrib = "0.4.0" 10 | reqwest = "0.9.4" 11 | rayon = "1.0.2" 12 | lazy_static = "1.1.0" 13 | regex = "1.0.5" 14 | tiny-keccak = "1.4.2" 15 | log = "0.4.5" 16 | time = "0.1" 17 | chrono = "0.4.6" 18 | petgraph = "0.4.13" 19 | evmexec = { path = "../evmexec", features = ["verbose"] } 20 | ethereum-newtypes = { path = "../evmexec/ethereum-newtypes" } 21 | parity_connector = { path = "../parity_connector" } 22 | subprocess = "0.1" 23 | ena = "0.10.1" 24 | yaml-rust = "0.4.2" 25 | bitflags = "1.0.4" 26 | fern = "0.5.6" 27 | serde = "1.0.79" 28 | serde_derive = "1.0.79" 29 | clap = "2.32.0" 30 | hexdecode = "0.2" 31 | crossbeam-channel = "0.5" 32 | crossbeam = "0.8" 33 | num_cpus = "1.8.0" 34 | rand = "0.5.5" 35 | serde_json = "1.0.32" 36 | parking_lot = "0.7.1" 37 | 38 | [dependencies.uint] 39 | version = "0.4" 40 | features = ["std"] 41 | 42 | [lib] 43 | path = "src/lib.rs" 44 | 45 | [[bin]] 46 | name = "ethbmc" 47 | path = "src/main.rs" 48 | 49 | [[bin]] 50 | name = "ethbmc-scheduler" 51 | path = "bin/scheduler.rs" 52 | 53 | [[bin]] 54 | name = "ethbmc-service" 55 | path = "bin/service.rs" 56 | 57 | [dev-dependencies] 58 | contracts = { path = "contracts" } 59 | 60 | [features] 61 | default = ["calls", "keccak", "memcopy"] 62 | 63 | # compile with statistics features 64 | stats = [] 65 | 66 | # compile with call simulation 67 | calls = [] 68 | 69 | # compile with keccak handling 70 | keccak = [] 71 | 72 | # compile with theory of memcopy utilised 73 | memcopy = [] 74 | -------------------------------------------------------------------------------- /esvm/benches/contracts.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate contracts; 3 | extern crate test; 4 | 5 | #[cfg(test)] 6 | mod benchmark_tests { 7 | use contracts::*; 8 | use test::Bencher; 9 | build_bench_tests!(); 10 | } 11 | -------------------------------------------------------------------------------- /esvm/bin/scheduler.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate esvm; 3 | extern crate fern; 4 | extern crate ethereum_newtypes; 5 | extern crate rayon; 6 | extern crate regex; 7 | extern crate reqwest; 8 | extern crate hexdecode; 9 | extern crate serde_json; 10 | 11 | #[macro_use] 12 | extern crate lazy_static; 13 | #[macro_use] 14 | extern crate log; 15 | extern crate chrono; 16 | 17 | use std::fs::{self, File}; 18 | use std::io::{BufWriter, Read, Write}; 19 | use std::sync::{ 20 | atomic::{AtomicUsize, Ordering}, 21 | Arc, Mutex, 22 | }; 23 | use std::thread; 24 | use std::time::Duration; 25 | 26 | use esvm::{AttackType, TimeoutAnalysis, AnalysisSuccess}; 27 | 28 | use serde_json::json; 29 | use chrono::prelude::Local; 30 | use clap::{App, Arg, ArgMatches}; 31 | use ethereum_newtypes::{Address}; 32 | use regex::Regex; 33 | use reqwest::Client; 34 | 35 | fn init_logger() -> Result<()> { 36 | fern::Dispatch::new() 37 | // Perform allocation-free log formatting 38 | .format(|out, message, record| { 39 | out.finish(format_args!( 40 | "{}[{}][{}] {}", 41 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), 42 | record.target(), 43 | record.level(), 44 | message 45 | )) 46 | }) 47 | // Add blanket level filter - 48 | .level(log::LevelFilter::Info) 49 | // Output to stdout, files, and other Dispatch configurations 50 | .chain(std::io::stdout()) 51 | .chain(fern::log_file("log/evmse-scheduler.log")?) 52 | // Apply globally 53 | .apply()?; 54 | Ok(()) 55 | } 56 | 57 | #[derive(Debug)] 58 | struct Worker { 59 | client: Client, 60 | url: String, 61 | timeout: Duration, 62 | } 63 | 64 | impl Worker { 65 | fn new(url: &str, timeout: usize) -> Result { 66 | let client = reqwest::Client::builder().timeout(None).build()?; 67 | let mut url = format!("{}/analyze_address", url); 68 | if timeout > 0 { 69 | url.push_str("_timeout"); 70 | } 71 | let timeout = Duration::from_secs((timeout * 60) as u64); 72 | Ok(Worker { 73 | client, 74 | url: url, 75 | timeout, 76 | }) 77 | } 78 | 79 | fn analyze(&self, address: Address) -> Result { 80 | info!("Analyzing {:x}", address.0); 81 | let mut res = if self.timeout > Duration::from_secs(0) { 82 | self 83 | .client 84 | .post(&self.url) 85 | .json(&TimeoutAnalysis { address, timeout: self.timeout}) 86 | .send()? 87 | } else { 88 | self 89 | .client 90 | .post(&self.url) 91 | .json(&address) 92 | .send()? 93 | }; 94 | Ok(res.json()?) 95 | } 96 | 97 | fn check_alive(&self) -> Result<()> { 98 | self.client 99 | .get(&format!("{}/alive", &self.url)) 100 | .send() 101 | .map_err(|e| e.into()) 102 | .map(|_| ()) 103 | } 104 | } 105 | 106 | struct WorkerHandle<'a> { 107 | worker: Option, 108 | scheduler: &'a Scheduler, 109 | kill: bool, 110 | } 111 | 112 | impl<'a> WorkerHandle<'a> { 113 | // specifically consume the handle to force readding the worker 114 | fn analyze(mut self, addr: Address) -> Result { 115 | let res = self.worker.as_ref().unwrap().analyze(addr); 116 | if let Err(ref error) = res { 117 | error!("Error analyzing {:x?}, checking worker!", error); 118 | if let Err(_) = self.worker.as_ref().unwrap().check_alive() { 119 | error!("Worker died analyzing {:x?}, shuting down worker!", error); 120 | self.kill = true; 121 | } else { 122 | return Err(Error::retry()); 123 | } 124 | } 125 | res 126 | } 127 | } 128 | 129 | impl<'a> Drop for WorkerHandle<'a> { 130 | fn drop(&mut self) { 131 | if !self.kill { 132 | let worker = self 133 | .worker 134 | .take() 135 | .expect("Worker replaced before adding back"); 136 | self.scheduler.add_worker(worker) 137 | } else { 138 | self.worker 139 | .take() 140 | .expect("Worker replaced before adding back"); 141 | } 142 | } 143 | } 144 | 145 | #[derive(Debug)] 146 | struct Scheduler { 147 | queue: Arc>>, 148 | } 149 | 150 | impl Scheduler { 151 | fn new() -> Self { 152 | let queue = Arc::new(Mutex::new(Vec::new())); 153 | Self { queue } 154 | } 155 | 156 | fn with_worker_count(urls: Vec, timeout: usize) -> Self { 157 | let s = Scheduler::new(); 158 | for url in &urls { 159 | s.queue.lock().unwrap().push(Worker::new(url, timeout).unwrap()); // if the workers can not connect initially fail 160 | } 161 | s 162 | } 163 | 164 | fn add_worker(&self, worker: Worker) { 165 | self.queue.lock().unwrap().push(worker); 166 | } 167 | 168 | fn get_worker(&self) -> WorkerHandle { 169 | let worker; 170 | loop { 171 | if let Some(w) = self.queue.lock().unwrap().pop() { 172 | worker = Some(w); 173 | break; 174 | } 175 | } 176 | WorkerHandle { 177 | worker, 178 | scheduler: self, 179 | kill: false, 180 | } 181 | } 182 | } 183 | 184 | type Result = ::std::result::Result; 185 | 186 | #[derive(Debug)] 187 | struct Error { 188 | kind: Kind, 189 | } 190 | 191 | impl Error { 192 | fn from_str(s: String) -> Self { 193 | Self { 194 | kind: Kind::Execution(s), 195 | } 196 | } 197 | 198 | fn retry() -> Self { 199 | Self { 200 | kind: Kind::Retry, 201 | } 202 | } 203 | 204 | fn kind(&self) -> &Kind { 205 | &self.kind 206 | } 207 | } 208 | 209 | macro_rules! impl_error_kind { 210 | ( 211 | $(#[$struct_attr:meta])* 212 | enum Kind { 213 | $( $enum_variant_name:ident($error_type:path), )+ ; 214 | $( $single_variant_name:ident, )+ 215 | } 216 | ) => { 217 | // meta attributes 218 | $(#[$struct_attr])* 219 | // enum definition 220 | enum Kind { 221 | $( $enum_variant_name($error_type), )+ 222 | $( $single_variant_name, )+ 223 | } 224 | 225 | // impl error conversion for each type 226 | $( 227 | impl ::std::convert::From<$error_type> for Error { 228 | fn from(error: $error_type) -> Self { 229 | Self { 230 | kind: Kind::$enum_variant_name(error), 231 | } 232 | } 233 | } 234 | )+ 235 | }; 236 | } 237 | 238 | impl_error_kind!(#[derive(Debug)] 239 | enum Kind { 240 | Reqwest(reqwest::Error), 241 | SerdeJson(serde_json::Error), 242 | Log(log::SetLoggerError), 243 | IO(std::io::Error), 244 | Execution(String), ; 245 | Retry, 246 | }); 247 | 248 | fn parse_args<'a>() -> ArgMatches<'a> { 249 | App::new("EthAEG scheduler for analyzing a large list of contracts") 250 | .arg( 251 | Arg::with_name("INPUT") 252 | .help("Set the list of accounts to scan") 253 | .required(true) 254 | .index(1), 255 | ) 256 | .arg( 257 | Arg::with_name("SERVER_LIST") 258 | .help("Set the list of backend servers") 259 | .required(true) 260 | .index(2), 261 | ) 262 | .arg(Arg::with_name("timeout").long("timeout").takes_value(true).help("Specify a timeout for the analysis, none used by default")) 263 | .arg(Arg::with_name("json").long("json").help("Dump the analysis result in json format.")) 264 | .get_matches() 265 | } 266 | 267 | fn parse_account_list(path: &str) -> (Arc>>, usize) { 268 | let mut acc_list = String::new(); 269 | File::open(path) 270 | .expect("Could not open account list") 271 | .read_to_string(&mut acc_list) 272 | .expect("Could not read account list"); 273 | let acc_vec: Vec<(usize, String)> = acc_list 274 | .lines() 275 | .filter_map(|line| match ACC_RE.captures(line) { 276 | Some(cap) => { 277 | let capture = cap.get(0).unwrap().as_str(); 278 | Some((0, capture.to_string())) 279 | } 280 | None => { 281 | warn!("Could not process: {}", line); 282 | None 283 | } 284 | }) 285 | .collect(); 286 | let len = acc_vec.len(); 287 | (Arc::new(Mutex::new(acc_vec)), len) 288 | } 289 | 290 | fn parse_server_list(path: &str) -> Vec { 291 | let mut server_list = String::new(); 292 | File::open(path) 293 | .expect("Could not open server list") 294 | .read_to_string(&mut server_list) 295 | .expect("Could not read server list"); 296 | server_list 297 | .lines() 298 | .map(|line| { 299 | let line = line.trim(); 300 | if line.starts_with("http") || line.starts_with("https") { 301 | line.to_string() 302 | } else { 303 | format!("http://{}", line) 304 | } 305 | }) 306 | .collect() 307 | } 308 | 309 | lazy_static! { 310 | static ref ACC_RE: Regex = Regex::new(r"0x[A-za-z0-9]{40}").unwrap(); 311 | } 312 | 313 | fn execute( 314 | work_stack: Arc>>, 315 | scheduler: Arc, 316 | counter: Arc, 317 | acc_len: usize, 318 | root_path: Arc, 319 | csv: &Mutex>, 320 | json: bool, 321 | ) -> Result<()> { 322 | loop { 323 | let (c, acc) = match work_stack.lock().unwrap().pop() { 324 | Some(work) => work, 325 | None => { 326 | info!("Could not fetch new work, exiting loop!"); 327 | return Ok(()); 328 | } 329 | }; 330 | 331 | if c >= 5 { 332 | info!("Account {} seed {} times, discarding!", acc, c); 333 | continue; 334 | } 335 | 336 | let a = Address(hexdecode::decode(&acc.as_bytes()).unwrap().as_slice().into()); 337 | let worker = scheduler.get_worker(); 338 | let res = worker.analyze(a); 339 | 340 | match res { 341 | Ok(r) => { 342 | let file_path = if json { 343 | format!("{}/{}.json", root_path, acc) 344 | } else { 345 | format!("{}/{}", root_path, acc) 346 | }; 347 | let mut f = match File::create(file_path) { 348 | Ok(f) => f, 349 | Err(e) => { 350 | error!("Could not create file for {}: {:?}", acc, e); 351 | return Err(Error::from_str(format!( 352 | "Could not create file for {}: {:?}", 353 | acc, e 354 | ))); 355 | } 356 | }; 357 | if json { 358 | if let AnalysisSuccess::Success(ref analysis) = r { 359 | let mut res = (false, false, false); 360 | if let Some(ref attacks) = analysis.attacks { 361 | for attack in attacks { 362 | if attack.attack_type == AttackType::StealMoney { 363 | res.0 = true; 364 | } 365 | if attack.attack_type == AttackType::DeleteContract { 366 | res.1 = true; 367 | } 368 | if attack.attack_type == AttackType::HijackControlFlow { 369 | res.2 = true; 370 | } 371 | } 372 | } 373 | csv.lock().unwrap().write_all(format!("{:x}, {}, {}, {}\n", analysis.address, res.0, res.1, res.2).as_bytes()).expect("Could not write to csv file!"); 374 | 375 | } 376 | let _write_res = f.write_all(json!(r).to_string().as_bytes()); 377 | } else { 378 | let content = match r { 379 | AnalysisSuccess::Success(analysis) => { 380 | let mut res = (false, false, false); 381 | if let Some(ref attacks) = analysis.attacks { 382 | for attack in attacks { 383 | if attack.attack_type == AttackType::StealMoney { 384 | res.0 = true; 385 | } 386 | if attack.attack_type == AttackType::DeleteContract { 387 | res.1 = true; 388 | } 389 | if attack.attack_type == AttackType::HijackControlFlow { 390 | res.2 = true; 391 | } 392 | } 393 | } 394 | csv.lock().unwrap().write_all(format!("{:x}, {}, {}, {}\n", analysis.address, res.0, res.1, res.2).as_bytes()).expect("Could not write to csv file!"); 395 | 396 | format!("{}", analysis) 397 | }, 398 | AnalysisSuccess::Failure(s) => { 399 | warn!("Failure during analysing {}: {}", acc, s); 400 | s 401 | }, 402 | AnalysisSuccess::Timeout => { 403 | warn!("Timeout during analysis: {:?}", acc); 404 | format!("Timeout analysing {:?}", acc) 405 | }, 406 | }; 407 | let _write_res = f.write_all(content.as_bytes()); 408 | } 409 | } 410 | Err(e) => { 411 | if let Kind::Retry = e.kind() { 412 | error!("Error analyzing {}, retrying...", acc); 413 | work_stack.lock().unwrap().push((c+1, acc)); 414 | } else { 415 | error!("Error analyzing {}: {:?} worker died!", acc, e); 416 | } 417 | } 418 | }; 419 | info!( 420 | "Analyzed {} of {} contracts", 421 | counter.fetch_add(1, Ordering::Relaxed), 422 | acc_len 423 | ); 424 | } 425 | } 426 | 427 | fn main() { 428 | // init logger 429 | init_logger().expect("Could not initialize logger"); 430 | 431 | // parse args 432 | let matches = parse_args(); 433 | 434 | // create root path 435 | let root_path = format!( 436 | "analysis/{}/", 437 | Local::now().format("%Y-%m-%d-%H:%M:%S").to_string() 438 | ); 439 | fs::create_dir_all(root_path.clone()).expect("Could not create root folder for analysis"); 440 | let root_path = Arc::new(root_path); 441 | 442 | let acc_path = matches.value_of("INPUT").unwrap(); 443 | let server_path = matches.value_of("SERVER_LIST").unwrap(); 444 | let (work_stack, acc_len) = parse_account_list(acc_path); 445 | let server_list = parse_server_list(server_path); 446 | let server_len = server_list.len(); 447 | let timeout = if let Some(b) = matches.value_of("timeout") { 448 | b.parse().expect("Incorrect timeout supplied!") 449 | } else { 450 | 0 451 | }; 452 | 453 | let scheduler = Arc::new(Scheduler::with_worker_count(server_list, timeout)); 454 | let counter = Arc::new(AtomicUsize::new(1)); 455 | 456 | let mut f = File::create(format!("{}/analysis.csv", root_path)).expect("Could not create csv file!"); 457 | f.write_all("address, steal ether, trigger suicide, hijack control flow\n".as_bytes()).expect("Could not write header to cvs!"); 458 | let csv_writer = Arc::new(Mutex::new(BufWriter::new(f))); 459 | 460 | info!("Starting Analysis"); 461 | let mut threads = Vec::new(); 462 | for _ in 0..server_len { 463 | let work_stack_clone = Arc::clone(&work_stack); 464 | let scheduler_clone = Arc::clone(&scheduler); 465 | let counter_clone = Arc::clone(&counter); 466 | let root_path_clone = Arc::clone(&root_path); 467 | let csv_clone = Arc::clone(&csv_writer); 468 | let json = matches.is_present("json"); 469 | let join_handle = thread::spawn(move || { 470 | execute( 471 | work_stack_clone, 472 | scheduler_clone, 473 | counter_clone, 474 | acc_len, 475 | root_path_clone, 476 | &csv_clone, 477 | json, 478 | ) 479 | }); 480 | threads.push(join_handle); 481 | } 482 | csv_writer.lock().unwrap().flush().expect("Could not finally flush writer"); 483 | 484 | for handle in threads { 485 | let _res = handle.join(); 486 | } 487 | info!("Finished Analysis"); 488 | } 489 | -------------------------------------------------------------------------------- /esvm/bin/service.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_hygiene, decl_macro)] 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | extern crate chrono; 7 | extern crate clap; 8 | extern crate esvm; 9 | extern crate ethereum_newtypes; 10 | extern crate fern; 11 | extern crate num_cpus; 12 | #[macro_use] 13 | extern crate rocket; 14 | extern crate subprocess; 15 | 16 | use std::{env, io::Read}; 17 | 18 | use clap::{App, ArgMatches}; 19 | use esvm::{ 20 | symbolic_analysis, AnalysisResult, AnalysisSuccess, SeEnviroment, Solvers, TimeoutAnalysis, 21 | CONFIG, 22 | }; 23 | use ethereum_newtypes::Address; 24 | use rocket_contrib::json::Json; 25 | use subprocess::{Exec, Redirection}; 26 | 27 | static SOLVER_TIMEOUT: usize = 120_000; 28 | 29 | fn init_logger() -> Result<(), fern::InitError> { 30 | fern::Dispatch::new() 31 | // Perform allocation-free log formatting 32 | .format(|out, message, record| { 33 | out.finish(format_args!( 34 | "{}[{}][{}] {}", 35 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), 36 | record.target(), 37 | record.level(), 38 | message 39 | )) 40 | }) 41 | // Add blanket level filter - 42 | .level(log::LevelFilter::Info) 43 | // Output to stdout, files, and other Dispatch configurations 44 | .chain(std::io::stdout()) 45 | .chain(fern::log_file("log/evmse-service.log")?) 46 | // Apply globally 47 | .apply()?; 48 | Ok(()) 49 | } 50 | 51 | fn build_args(message: &Json) -> Vec { 52 | let mut args = vec![ 53 | "-x".to_string(), 54 | format!("{:x}", message.0.address), 55 | "--json".to_string(), 56 | ]; 57 | 58 | let mut cli_args = env::args(); 59 | cli_args.next(); // remove silly path 60 | args.append(&mut cli_args.collect()); 61 | args 62 | } 63 | 64 | #[post( 65 | "/analyze_address_timeout", 66 | format = "application/json", 67 | data = "" 68 | )] 69 | fn analyze_address_timeout(message: Json) -> Json { 70 | let args = build_args(&message); 71 | 72 | let mut process = match Exec::cmd("ethaeg") 73 | .args(&args) 74 | .stderr(Redirection::Merge) // redirect err output to stdout 75 | .stdout(Redirection::Pipe) 76 | .popen() 77 | { 78 | Err(why) => panic!("couldn't spawn ethaeg: {}", why), 79 | Ok(process) => process, 80 | }; 81 | 82 | match process.wait_timeout(message.0.timeout) { 83 | Ok(result) => { 84 | process.kill().unwrap(); 85 | if let None = result { 86 | info!("Analysis for {:x} timed out!", message.0.address); 87 | return Json(AnalysisSuccess::Timeout); 88 | } 89 | } 90 | Err(error) => { 91 | return Json(AnalysisSuccess::Failure(format!( 92 | "Error waiting for subprocess: {}", 93 | error 94 | ))); 95 | } 96 | }; 97 | 98 | let mut buffer = String::new(); 99 | // we go now 100 | if let Err(_) = process.stdout.take().unwrap().read_to_string(&mut buffer) { 101 | return Json(AnalysisSuccess::Failure( 102 | "Could not capture ethaeg output".to_string(), 103 | )); 104 | } 105 | 106 | match serde_json::from_str(&buffer) { 107 | Ok(succ) => Json(AnalysisSuccess::Success(succ)), 108 | Err(error) => Json(AnalysisSuccess::Failure(format!( 109 | "Could not parse ethaeg output:\n{}\n{}", 110 | buffer, error 111 | ))), 112 | } 113 | } 114 | 115 | #[post("/analyze_address", format = "application/json", data = "")] 116 | fn analyze_address(message: Json
) -> Json { 117 | let solver = Solvers::Yice { 118 | count: CONFIG.read().unwrap().cores, 119 | timeout: SOLVER_TIMEOUT, 120 | }; 121 | let result = analyze(solver, message.0); 122 | match result { 123 | Some(succ) => Json(AnalysisSuccess::Success(succ)), 124 | None => Json(AnalysisSuccess::Failure( 125 | "Could not create environment for analysis!".to_string(), 126 | )), 127 | } 128 | } 129 | 130 | #[get("/alive")] 131 | fn alive() -> &'static str { 132 | "staying alive" 133 | } 134 | 135 | fn analyze(pool: Solvers, addr: Address) -> Option { 136 | info!("Analyzing address: {:x}", addr.0); 137 | let se_env = SeEnviroment::from_chain(&format!("{:x}", addr.0))?; 138 | let config = CONFIG.read().unwrap().clone(); 139 | Some(symbolic_analysis(se_env, config, pool)) 140 | } 141 | 142 | fn rocket() -> rocket::Rocket { 143 | rocket::ignite().mount( 144 | "/analyses", 145 | routes![analyze_address, analyze_address_timeout, alive], 146 | ) 147 | } 148 | 149 | fn parse_args<'a>() -> ArgMatches<'a> { 150 | let app = App::new("EthAEG webservice"); 151 | let app = esvm::arguments(app); 152 | app.get_matches() 153 | } 154 | 155 | fn main() { 156 | init_logger().expect("Could not initialize logger"); 157 | let matches = parse_args(); 158 | esvm::set_global_config(&matches); 159 | rocket().launch(); 160 | } 161 | -------------------------------------------------------------------------------- /esvm/contracts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contracts" 3 | version = "0.1.0" 4 | authors = ["Anonymous "] 5 | 6 | [dependencies] 7 | lazy_static = "*" 8 | yaml-rust = "0.4.2" 9 | esvm = { path = "../"} 10 | num_cpus = "1.8.0" 11 | -------------------------------------------------------------------------------- /esvm/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub enum Instr { 3 | IStop, 4 | IAdd, 5 | IMul, 6 | ISub, 7 | IDiv, 8 | ISDiv, 9 | IMod, 10 | ISMod, 11 | IAddMod, 12 | IMulMod, 13 | IExp, 14 | ISext, // SIGNEXTEND 15 | ILt, 16 | IGt, 17 | ISLt, 18 | ISGt, 19 | IEql, 20 | IIsZero, 21 | IAnd, 22 | IOr, 23 | IXor, 24 | INot, 25 | IByte, 26 | IShl, 27 | IAShr, 28 | ILShr, 29 | ISHA3, 30 | IAddr, 31 | IBalance, 32 | IOrigin, 33 | ICaller, 34 | ICallValue, 35 | ICallDataLoad, 36 | ICallDataSize, 37 | ICallDataCopy, 38 | ICodeSize, 39 | ICodeCopy, 40 | IGasPrice, 41 | IExtCodeSize, 42 | IExtCodeCopy, 43 | IRDataSize, 44 | IRDataCopy, 45 | IBlockHash, 46 | ICoinBase, 47 | ITimeStamp, 48 | INumber, 49 | IDifficulty, 50 | IGasLimit, 51 | IPop, 52 | IMLoad, 53 | IMStore, 54 | IMStore8, 55 | ISLoad, 56 | ISStore, 57 | IJump, 58 | IJumpIf, 59 | IPC, 60 | IMSize, 61 | IGas, 62 | IJumpDest, 63 | IPush(Vec), 64 | IDup(usize), 65 | ISwap(usize), 66 | ILog(usize), 67 | ICreate, 68 | ICall, 69 | ICallCode, 70 | IReturn, 71 | IDelegateCall, 72 | ICreate2, 73 | IRevert, 74 | IStaticCall, 75 | ISelfDestruct, 76 | IInvalid, 77 | } 78 | 79 | impl Instr { 80 | pub fn size(&self) -> usize { 81 | match self { 82 | Instr::IPush(ref a) => a.len() + 1, 83 | _ => 1, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /esvm/src/disasm.rs: -------------------------------------------------------------------------------- 1 | use crate::bytecode::Instr; 2 | use std::collections::HashMap; 3 | use std::collections::HashSet; 4 | use std::io; 5 | use std::io::Cursor; 6 | use std::io::Read; 7 | 8 | #[derive(Clone)] 9 | pub struct Disasm { 10 | pub opcodes: HashMap, 11 | jump_targets: HashSet, 12 | } 13 | 14 | pub struct CodeCoverage { 15 | coverage: HashMap, 16 | } 17 | 18 | impl CodeCoverage { 19 | pub fn from_raw(data: &[u8]) -> Self { 20 | let mut reader = Cursor::new(data.to_owned()); 21 | let mut coverage = HashMap::new(); 22 | while let Ok((offset, _)) = Disasm::read_on_instr(&mut reader) { 23 | coverage.insert(offset, false); 24 | } 25 | CodeCoverage { coverage } 26 | } 27 | 28 | pub fn taint(&mut self, offset: usize) { 29 | if let Some(ins) = self.coverage.get_mut(&offset) { 30 | *ins = true; 31 | } 32 | } 33 | 34 | pub fn coverage(&self) -> f64 { 35 | self.coverage 36 | .values() 37 | .filter_map(|visited| if *visited { Some(1) } else { None }) 38 | .sum::() as f64 39 | / self.coverage.len() as f64 40 | } 41 | } 42 | 43 | impl Disasm { 44 | pub fn from_raw(data: &[u8]) -> Self { 45 | let mut reader = Cursor::new(data.to_owned()); 46 | let mut opcodes = HashMap::new(); 47 | while let Ok((offset, instr)) = Disasm::read_on_instr(&mut reader) { 48 | opcodes.insert(offset, instr); 49 | } 50 | let jump_targets = opcodes 51 | .keys() 52 | .filter_map(|a| { 53 | if opcodes[a] == Instr::IJumpDest { 54 | Some(*a) 55 | } else { 56 | None 57 | } 58 | }) 59 | .collect::>(); 60 | Disasm { 61 | opcodes, 62 | jump_targets, 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | pub fn new(opcode_vec: Vec) -> Self { 68 | let mut offset = 0; 69 | let mut opcodes = HashMap::new(); 70 | for i in opcode_vec { 71 | let size = i.size(); 72 | opcodes.insert(offset, i); 73 | offset += size; 74 | } 75 | let jump_targets = opcodes 76 | .keys() 77 | .filter_map(|a| { 78 | if opcodes[a] == Instr::IJumpDest { 79 | Some(*a) 80 | } else { 81 | None 82 | } 83 | }) 84 | .collect::>(); 85 | Disasm { 86 | opcodes, 87 | jump_targets, 88 | } 89 | } 90 | 91 | pub fn opcodes(&self) -> ::std::collections::hash_map::Values { 92 | self.opcodes.values() 93 | } 94 | 95 | pub fn jump_targets(&self) -> &HashSet { 96 | &self.jump_targets 97 | } 98 | 99 | pub fn is_jump_target(&self, addr: usize) -> bool { 100 | self.opcodes.get(&addr) == Some(&Instr::IJumpDest) 101 | } 102 | 103 | pub fn get_size(&self, addr: usize) -> Option { 104 | match self.opcodes.get(&addr) { 105 | Some(instr) => Some(instr.size()), 106 | None => None, 107 | } 108 | } 109 | 110 | pub fn get(&self, addr: usize) -> Option { 111 | self.opcodes.get(&addr).cloned() 112 | } 113 | 114 | #[allow(dead_code)] 115 | pub fn get_ordered_offsets(&self) -> Vec<&usize> { 116 | let mut ordered_offset: Vec<_> = self.opcodes.keys().collect(); 117 | ordered_offset.sort_by(|a, b| a.cmp(b)); 118 | ordered_offset 119 | } 120 | 121 | fn read_bytes(c: &mut Cursor>, n: usize) -> Result, io::Error> { 122 | let mut buffer = vec![0; n]; 123 | c.read_exact(&mut buffer)?; 124 | Ok(buffer) 125 | } 126 | 127 | fn read_byte(c: &mut Cursor>) -> Result { 128 | Ok(Disasm::read_bytes(c, 1)?[0]) 129 | } 130 | 131 | fn read_on_instr(c: &mut Cursor>) -> Result<(usize, Instr), io::Error> { 132 | let offset = c.position(); 133 | let byte = Disasm::read_byte(c)?; 134 | let instr = match byte { 135 | 0x00 => Instr::IStop, 136 | 0x01 => Instr::IAdd, 137 | 0x02 => Instr::IMul, 138 | 0x03 => Instr::ISub, 139 | 0x04 => Instr::IDiv, 140 | 0x05 => Instr::ISDiv, 141 | 0x06 => Instr::IMod, 142 | 0x07 => Instr::ISMod, 143 | 0x08 => Instr::IAddMod, 144 | 0x09 => Instr::IMulMod, 145 | 0x0a => Instr::IExp, 146 | 0x0b => Instr::ISext, 147 | //.. 148 | 0x10 => Instr::ILt, 149 | 0x11 => Instr::IGt, 150 | 0x12 => Instr::ISLt, 151 | 0x13 => Instr::ISGt, 152 | 0x14 => Instr::IEql, 153 | 0x15 => Instr::IIsZero, 154 | 0x16 => Instr::IAnd, 155 | 0x17 => Instr::IOr, 156 | 0x18 => Instr::IXor, 157 | 0x19 => Instr::INot, 158 | 0x1a => Instr::IByte, 159 | 0x1b => Instr::IShl, 160 | 0x1c => Instr::ILShr, 161 | 0x1d => Instr::IAShr, 162 | 0x20 => Instr::ISHA3, 163 | //... 164 | 0x30 => Instr::IAddr, 165 | 0x31 => Instr::IBalance, 166 | 0x32 => Instr::IOrigin, 167 | 0x33 => Instr::ICaller, 168 | 0x34 => Instr::ICallValue, 169 | 0x35 => Instr::ICallDataLoad, 170 | 0x36 => Instr::ICallDataSize, 171 | 0x37 => Instr::ICallDataCopy, 172 | 0x38 => Instr::ICodeSize, 173 | 0x39 => Instr::ICodeCopy, 174 | 0x3a => Instr::IGasPrice, 175 | 0x3b => Instr::IExtCodeSize, 176 | 0x3c => Instr::IExtCodeCopy, 177 | 0x3d => Instr::IRDataSize, 178 | 0x3e => Instr::IRDataCopy, 179 | //... 180 | 0x40 => Instr::IBlockHash, 181 | 0x41 => Instr::ICoinBase, 182 | 0x42 => Instr::ITimeStamp, 183 | 0x43 => Instr::INumber, 184 | 0x44 => Instr::IDifficulty, 185 | 0x45 => Instr::IGasLimit, 186 | //... 187 | 0x50 => Instr::IPop, 188 | 0x51 => Instr::IMLoad, 189 | 0x52 => Instr::IMStore, 190 | 0x53 => Instr::IMStore8, 191 | 0x54 => Instr::ISLoad, 192 | 0x55 => Instr::ISStore, 193 | 0x56 => Instr::IJump, 194 | 0x57 => Instr::IJumpIf, 195 | 0x58 => Instr::IPC, 196 | 0x59 => Instr::IMSize, 197 | 0x5a => Instr::IGas, 198 | 0x5b => Instr::IJumpDest, 199 | //... 200 | 0x60 | 0x61 | 0x62 | 0x63 | 0x64 | 0x65 | 0x66 | 0x67 | 0x68 | 0x69 | 0x6a | 0x6b 201 | | 0x6c | 0x6d | 0x6e | 0x6f => { 202 | Instr::IPush(Disasm::read_bytes(c, 1 + (byte & 0x0f) as usize)?) 203 | } 204 | 0x70 | 0x71 | 0x72 | 0x73 | 0x74 | 0x75 | 0x76 | 0x77 | 0x78 | 0x79 | 0x7a | 0x7b 205 | | 0x7c | 0x7d | 0x7e | 0x7f => { 206 | Instr::IPush(Disasm::read_bytes(c, (0x11 + (byte & 0x0f)) as usize)?) 207 | } 208 | 0x80 => Instr::IDup(1), 209 | 0x81 => Instr::IDup(2), 210 | 0x82 => Instr::IDup(3), 211 | 0x83 => Instr::IDup(4), 212 | 0x84 => Instr::IDup(5), 213 | 0x85 => Instr::IDup(6), 214 | 0x86 => Instr::IDup(7), 215 | 0x87 => Instr::IDup(8), 216 | 0x88 => Instr::IDup(9), 217 | 0x89 => Instr::IDup(10), 218 | 0x8a => Instr::IDup(11), 219 | 0x8b => Instr::IDup(12), 220 | 0x8c => Instr::IDup(13), 221 | 0x8d => Instr::IDup(14), 222 | 0x8e => Instr::IDup(15), 223 | 0x8f => Instr::IDup(16), 224 | //... 225 | 0x90 => Instr::ISwap(1), 226 | 0x91 => Instr::ISwap(2), 227 | 0x92 => Instr::ISwap(3), 228 | 0x93 => Instr::ISwap(4), 229 | 0x94 => Instr::ISwap(5), 230 | 0x95 => Instr::ISwap(6), 231 | 0x96 => Instr::ISwap(7), 232 | 0x97 => Instr::ISwap(8), 233 | 0x98 => Instr::ISwap(9), 234 | 0x99 => Instr::ISwap(10), 235 | 0x9a => Instr::ISwap(11), 236 | 0x9b => Instr::ISwap(12), 237 | 0x9c => Instr::ISwap(13), 238 | 0x9d => Instr::ISwap(14), 239 | 0x9e => Instr::ISwap(15), 240 | 0x9f => Instr::ISwap(16), 241 | //... 242 | 0xa0 => Instr::ILog(0), 243 | 0xa1 => Instr::ILog(1), 244 | 0xa2 => Instr::ILog(2), 245 | 0xa3 => Instr::ILog(3), 246 | 0xa4 => Instr::ILog(4), 247 | //... 248 | 0xf0 => Instr::ICreate, 249 | 0xf1 => Instr::ICall, 250 | 0xf2 => Instr::ICallCode, 251 | 0xf3 => Instr::IReturn, 252 | 0xf4 => Instr::IDelegateCall, 253 | 0xfb => Instr::ICreate2, 254 | 0xfd => Instr::IRevert, 255 | 0xfa => Instr::IStaticCall, 256 | 0xff => Instr::ISelfDestruct, 257 | 0xfe | _ => Instr::IInvalid, 258 | }; 259 | Ok((offset as usize, instr)) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /esvm/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | extern crate clap; 3 | extern crate esvm; 4 | #[macro_use] 5 | extern crate log; 6 | extern crate fern; 7 | extern crate num_cpus; 8 | extern crate parity_connector; 9 | extern crate yaml_rust; 10 | 11 | #[macro_use] 12 | extern crate serde_json; 13 | 14 | use std::env; 15 | use std::fs::{self, File}; 16 | use std::io::Read; 17 | 18 | use chrono::prelude::*; 19 | use clap::{App, Arg, ArgMatches}; 20 | use yaml_rust::YamlLoader; 21 | 22 | use esvm::{symbolic_analysis, Attack, AttackType, SeEnviroment, Solvers, CONFIG}; 23 | 24 | fn init_logger(json_mode: bool) -> Result<(), fern::InitError> { 25 | fs::create_dir_all("log"); 26 | let level = match env::var_os("RUST_LOG") { 27 | Some(level) => match level.to_str().unwrap() { 28 | "info" => log::LevelFilter::Info, 29 | "debug" => log::LevelFilter::Debug, 30 | "trace" => log::LevelFilter::Trace, 31 | _ => panic!("Declared invalid logging level!"), 32 | }, 33 | None => log::LevelFilter::Info, 34 | }; 35 | let mut builder = fern::Dispatch::new() 36 | // Perform allocation-free log formatting 37 | .format(|out, message, record| { 38 | out.finish(format_args!( 39 | "{}[{}][{}] {}", 40 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), 41 | record.target(), 42 | record.level(), 43 | message 44 | )) 45 | }) 46 | // Add blanket level filter - 47 | .level(level) 48 | // allways log to log file 49 | .chain(fern::log_file("log/evmse.log")?); 50 | if !json_mode { 51 | builder = builder.chain(std::io::stdout()); 52 | } 53 | 54 | builder.apply()?; 55 | Ok(()) 56 | } 57 | 58 | pub fn main() { 59 | // init logger 60 | let matches = parse_args(); 61 | init_logger(matches.is_present("json")).expect("Could not initialize logger"); 62 | analysis(matches); 63 | } 64 | 65 | fn analysis(matches: ArgMatches) { 66 | // block people from being dumb 67 | assert!( 68 | !(matches.is_present("all_optimizations") && matches.is_present("disable_optimizations")) 69 | ); 70 | 71 | esvm::set_global_config(&matches); 72 | if matches.is_present("list") { 73 | list_analysis(matches); 74 | } else { 75 | single_analysis(matches); 76 | } 77 | } 78 | 79 | fn list_analysis(matches: clap::ArgMatches) { 80 | { 81 | let g_config = CONFIG.read().unwrap(); 82 | if g_config.parity.is_none() { 83 | panic!("On chain analysis must be run in conjunction with a parity node"); 84 | } 85 | } 86 | let input = matches.value_of("INPUT").unwrap(); 87 | let accounts = parse_input_list(input); 88 | 89 | let mut results = vec![]; 90 | 91 | for (i, acc) in accounts.iter().enumerate() { 92 | info!("========================================================="); 93 | info!("Analyzing account {} of {}", i + 1, accounts.len()); 94 | info!("========================================================="); 95 | if let Some(se_env) = SeEnviroment::from_chain(&acc) { 96 | let config = CONFIG.read().unwrap().clone(); 97 | 98 | let pool = if let Some(solver) = matches.value_of("solver") { 99 | match solver { 100 | "z3" => Solvers::Z3 { 101 | count: CONFIG.read().unwrap().cores, 102 | timeout: CONFIG.read().unwrap().solver_timeout, 103 | }, 104 | "boolector" => Solvers::Boolector { 105 | count: CONFIG.read().unwrap().cores, 106 | timeout: CONFIG.read().unwrap().solver_timeout, 107 | }, 108 | "yice" => Solvers::Yice { 109 | count: CONFIG.read().unwrap().cores, 110 | timeout: CONFIG.read().unwrap().solver_timeout, 111 | }, 112 | _ => panic!("Supplied incorrect solver name"), 113 | } 114 | } else { 115 | Solvers::Yice { 116 | count: CONFIG.read().unwrap().cores, 117 | timeout: CONFIG.read().unwrap().solver_timeout, 118 | } 119 | }; 120 | let ana_res = symbolic_analysis(se_env, config, pool); 121 | 122 | info!("========================================================="); 123 | for l in format!("{}", ana_res).lines() { 124 | info!("{}", l); 125 | } 126 | info!("=========================================================\n\n\n"); 127 | results.push((acc.clone(), ana_res.attacks)); 128 | } else { 129 | info!("========================================================="); 130 | info!( 131 | "Could not create env for {} (account selfdestructed?).", 132 | acc 133 | ); 134 | info!("========================================================="); 135 | } 136 | } 137 | dump_result(results) 138 | } 139 | 140 | fn dump_result(results: Vec<(String, Option>)>) { 141 | let mut content = String::new(); 142 | 143 | // preamble 144 | content.push_str("address, steal ether, trigger suicide, hijack control flow\n"); 145 | 146 | for (acc, attacks) in &results { 147 | let mut res = (false, false, false); 148 | if let Some(attacks) = attacks { 149 | for attack in attacks { 150 | if attack.attack_type == AttackType::StealMoney { 151 | res.0 = true; 152 | } 153 | if attack.attack_type == AttackType::DeleteContract { 154 | res.1 = true; 155 | } 156 | if attack.attack_type == AttackType::HijackControlFlow { 157 | res.2 = true; 158 | } 159 | } 160 | } 161 | content.push_str(&format!("{}, {}, {}, {}\n", acc, res.0, res.1, res.2)); 162 | } 163 | 164 | let dt = Local::now(); 165 | let file_name = dt.format("%Y-%m-%d-%H:%M:%S").to_string(); 166 | fs::write( 167 | &format!("output/scan/{}-{}.csv", results.len(), file_name), 168 | &content, 169 | ) 170 | .expect("Could not dump analysis results"); 171 | } 172 | 173 | // assumes hex encoded ethereum addresses 174 | fn parse_input_list(path: &str) -> Vec { 175 | let mut f = File::open(path).unwrap(); 176 | let mut s = String::new(); 177 | f.read_to_string(&mut s).unwrap(); 178 | s.lines() 179 | .map(|s| s.trim().trim_matches('\n').to_string()) 180 | .collect() 181 | } 182 | 183 | fn single_analysis(matches: clap::ArgMatches) { 184 | let se_env; 185 | let input = matches.value_of("INPUT").unwrap(); 186 | if matches.is_present("account") { 187 | { 188 | let g_config = CONFIG.read().unwrap(); 189 | if g_config.parity.is_none() { 190 | panic!("On chain analysis must be run in conjunction with a parity node"); 191 | } 192 | if let Some(env) = SeEnviroment::from_chain(input) { 193 | se_env = env; 194 | } else { 195 | info!("========================================================="); 196 | info!( 197 | "Could not create env for {} (account selfdestructed?).", 198 | input 199 | ); 200 | info!("========================================================="); 201 | return; 202 | } 203 | } 204 | } else { 205 | let mut f = File::open(input).unwrap(); 206 | let mut s = String::new(); 207 | f.read_to_string(&mut s).unwrap(); 208 | let yaml = YamlLoader::load_from_str(&s).unwrap(); 209 | se_env = SeEnviroment::from_yaml(&yaml[0]); 210 | } 211 | 212 | let config = CONFIG.read().unwrap().clone(); 213 | 214 | let pool = if let Some(solver) = matches.value_of("solver") { 215 | match solver { 216 | "z3" => Solvers::Z3 { 217 | count: CONFIG.read().unwrap().cores, 218 | timeout: CONFIG.read().unwrap().solver_timeout, 219 | }, 220 | "boolector" => Solvers::Boolector { 221 | count: CONFIG.read().unwrap().cores, 222 | timeout: CONFIG.read().unwrap().solver_timeout, 223 | }, 224 | "yice" => Solvers::Yice { 225 | count: CONFIG.read().unwrap().cores, 226 | timeout: CONFIG.read().unwrap().solver_timeout, 227 | }, 228 | _ => panic!("Supplied incorrect solver name"), 229 | } 230 | } else { 231 | Solvers::Yice { 232 | count: CONFIG.read().unwrap().cores, 233 | timeout: CONFIG.read().unwrap().solver_timeout, 234 | } 235 | }; 236 | 237 | let res = symbolic_analysis(se_env, config, pool); 238 | if matches.is_present("json") { 239 | println!("{}", json!(res)); 240 | } else { 241 | for l in format!("{}", res).lines() { 242 | info!("{}", l); 243 | } 244 | } 245 | } 246 | 247 | fn parse_args<'a>() -> ArgMatches<'a> { 248 | let app = App::new("EthBMC") 249 | .version("1.0.0") 250 | .about("EthBMC: A Bounded Model Checker for Smart Contracts") 251 | // General 252 | .arg( 253 | Arg::with_name("INPUT") 254 | .help("Set input file / address") 255 | .required(true) 256 | .index(1), 257 | ) 258 | .arg(Arg::with_name("json").long("json").help("Output json without logging")) 259 | .arg(Arg::with_name("account").long("acc").short("x").help("The input is an Ethereum account address, must be used with parity backend, mainnet only")) 260 | .arg(Arg::with_name("solver").long("solver").takes_value(true).help("The SMT solver to use: z3, boolector, yices2 [yices2]")) 261 | .arg(Arg::with_name("list").long("list").help("The input is a list of Ethereum account address, writes the result to a csv file in the output folder")); 262 | let app = esvm::arguments(app); 263 | app.get_matches() 264 | } 265 | -------------------------------------------------------------------------------- /esvm/src/se/config.rs: -------------------------------------------------------------------------------- 1 | pub const ORIGIN: &str = "79802072816451330120090824003621134189783447932"; 2 | pub const TARGET_ADDR: &str = "870709263458102366179684276445190559371821507294"; 3 | pub const HIJACK_ADDR: &str = "1425888768636756950564344006058156923788947829645"; 4 | pub const MAX_CALLVAL: &str = "10000000000000000000"; 5 | pub const MAX_GASPRICE: &str = "1000000"; 6 | pub const MAX_GAS: &str = "20000000000000"; 7 | pub const GAS_LIMIT: &str = "20000000000000"; 8 | pub const MAX_DIFFICULTY: &str = "123012123123"; 9 | pub const MAX_NUMBER: &str = "12312312312"; 10 | pub const MAX_TIMESTAMP: &str = "123123123122"; 11 | pub const MAX_CALLDATA_SIZE: &str = "256"; 12 | pub const COINBASE: &str = 13 | "63567725099261988277993533668138608275708455429142357959792648832739515514623"; 14 | pub const BLOCKHASH: &str = 15 | "1238602313824588160051031710043776340099843562868198520123683011552894665916"; 16 | -------------------------------------------------------------------------------- /esvm/src/se/expr/boolector.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::DefaultHasher, 3 | fs::File, 4 | hash::{Hash, Hasher}, 5 | io::Write, 6 | }; 7 | 8 | use regex::Regex; 9 | use subprocess::{Exec, Redirection}; 10 | 11 | use crate::se::{ 12 | expr::{ 13 | bval::{const256, BVal}, 14 | solver::Solver, 15 | }, 16 | symbolic_analysis::CONFIG, 17 | }; 18 | 19 | lazy_static! { 20 | static ref VALUE_RE: Regex = 21 | Regex::new(r"\(\((?P(.*)) \(_ bv(?P([0-9]*)) (256|8)\)\)\)").unwrap(); 22 | } 23 | 24 | pub struct BoolectorInstance { 25 | timeout: usize, 26 | input_buffer: String, 27 | dump: bool, 28 | } 29 | 30 | impl BoolectorInstance { 31 | pub fn new(timeout: usize) -> Self { 32 | let input_buffer = String::from("(set-logic QF_ABV)\n"); 33 | let timeout = timeout / 1_000; // boolector uses seconds 34 | let dump = CONFIG.read().unwrap().dump_solver; 35 | Self { 36 | timeout, 37 | input_buffer, 38 | dump, 39 | } 40 | } 41 | 42 | fn communicate(&self) -> String { 43 | Exec::cmd("boolector") 44 | .arg(format!("--time={}", self.timeout).as_str()) 45 | .arg("-m") 46 | .arg("-d") 47 | .stdin(self.input_buffer.as_str()) 48 | .stderr(Redirection::Merge) 49 | .stdout(Redirection::Pipe) 50 | .capture() 51 | .unwrap() 52 | .stdout_str() 53 | } 54 | 55 | fn check(&self, output: &str) -> bool { 56 | if output.contains("boolector") { 57 | debug!( 58 | "Boolector error: {}, for input: {}", 59 | output, self.input_buffer 60 | ); 61 | false 62 | } else if output.contains("unsat") { 63 | false 64 | } else if output.contains("sat") { 65 | true 66 | } else if output.contains("unknown") { 67 | warn!("Solver timed out after {} seconds", self.timeout); 68 | false 69 | } else { 70 | debug!( 71 | "Strange boolector output: {}, for input: {}", 72 | output, self.input_buffer 73 | ); 74 | false 75 | } 76 | } 77 | } 78 | 79 | impl Solver for BoolectorInstance { 80 | fn push_formula(&mut self, formula: &str) { 81 | self.input_buffer.push_str(&format!("{}\n", formula)); 82 | } 83 | 84 | fn check_sat(&mut self) -> bool { 85 | self.input_buffer.push_str("(check-sat)\n"); 86 | self.check(&self.communicate()) 87 | } 88 | 89 | fn get_value(&mut self, value: &str) -> Option { 90 | self.input_buffer.push_str("(check-sat)\n"); 91 | self.input_buffer 92 | .push_str(&format!("(get-value ({}))\n", value)); 93 | let output = self.communicate(); 94 | 95 | if !self.check(&output) { 96 | return None; 97 | } 98 | 99 | let caps = if let Some(x) = VALUE_RE.captures(&output) { 100 | x 101 | } else { 102 | return None; 103 | }; 104 | caps.name("value").map(|x| const256(x.as_str())) 105 | } 106 | 107 | fn get_values(&mut self, values: &[String]) -> Option> { 108 | self.input_buffer.push_str("(check-sat)\n"); 109 | for value in values { 110 | self.input_buffer 111 | .push_str(&format!("(get-value ({}))\n", value)); 112 | } 113 | let output = self.communicate(); 114 | 115 | if !self.check(&output) { 116 | return None; 117 | } 118 | 119 | let mut result = Vec::with_capacity(values.len()); 120 | for cap in VALUE_RE.captures_iter(&output) { 121 | result.push(cap.name("value").map(|x| const256(x.as_str()))?); 122 | } 123 | 124 | Some(result) 125 | } 126 | 127 | fn reset(&mut self) { 128 | if self.dump { 129 | let mut s = DefaultHasher::new(); 130 | self.input_buffer.hash(&mut s); 131 | let f_name = format!("queries/{:x}.smt2", s.finish()); 132 | let mut file = File::create(f_name).unwrap(); 133 | file.write_all(self.input_buffer.as_bytes()).unwrap(); 134 | } 135 | 136 | self.input_buffer.clear(); 137 | self.input_buffer.push_str("(set-logic QF_ABV)\n") 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use super::*; 144 | use crate::se::expr::bval::const_usize; 145 | 146 | const TEST_TIMEOUT: usize = 120; 147 | 148 | #[test] 149 | fn boolector_general_test() { 150 | let test_formula = String::from( 151 | "(declare-const a (_ BitVec 256)) 152 | (declare-const b (_ BitVec 8)) 153 | (assert (= a (_ bv10 256))) 154 | (assert (= b ((_ extract 7 0) a) ))", 155 | ); 156 | let mut boolector = BoolectorInstance::new(TEST_TIMEOUT); 157 | for line in test_formula.lines() { 158 | boolector.push_formula(line); 159 | } 160 | assert_eq!(true, boolector.check_sat()); 161 | } 162 | 163 | #[test] 164 | fn boolector_general_false_test() { 165 | let f_false = String::from( 166 | "(declare-const a (_ BitVec 256)) 167 | (declare-const b (_ BitVec 256)) 168 | (assert (= a (_ bv10 256))) 169 | (assert (= b (_ bv11 256))) 170 | (assert (= a b))", 171 | ); 172 | let mut boolector = BoolectorInstance::new(TEST_TIMEOUT); 173 | for line in f_false.lines() { 174 | boolector.push_formula(line); 175 | } 176 | assert_eq!(false, boolector.check_sat()); 177 | } 178 | 179 | #[test] 180 | fn boolector_get_value() { 181 | let mut boolector = BoolectorInstance::new(TEST_TIMEOUT); 182 | 183 | boolector.push_formula(&String::from("(declare-const a (_ BitVec 256))")); 184 | boolector.push_formula(&String::from("(assert (= a (_ bv10 256)))")); 185 | assert_eq!(Some(const_usize(10)), boolector.get_value("a")); 186 | } 187 | 188 | #[test] 189 | fn boolector_get_values() { 190 | let mut boolector = BoolectorInstance::new(TEST_TIMEOUT); 191 | 192 | boolector.push_formula(&String::from("(declare-const a (_ BitVec 256))")); 193 | boolector.push_formula(&String::from("(assert (= a (_ bv10 256)))")); 194 | boolector.push_formula(&String::from("(declare-const b (_ BitVec 256))")); 195 | boolector.push_formula(&String::from("(assert (= b (_ bv11 256)))")); 196 | assert_eq!( 197 | Some(vec!(const_usize(10), const_usize(11))), 198 | boolector.get_values(&["a".to_owned(), String::from("b")]) 199 | ); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /esvm/src/se/expr/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod bval; 3 | pub mod boolector; 4 | pub mod formel_builder; 5 | pub mod solver; 6 | pub mod symbolic_memory; 7 | pub mod yice; 8 | pub mod z3; 9 | -------------------------------------------------------------------------------- /esvm/src/se/expr/solver.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, sync::Arc}; 2 | 3 | use crossbeam::queue::SegQueue; 4 | 5 | use crate::se::expr::{ 6 | boolector::BoolectorInstance, bval::BVal, formel_builder::SmtLib2Builder, yice::YiceInstance, 7 | z3::Z3Instance, 8 | }; 9 | 10 | pub trait Solver: Send { 11 | /// Add fomula(e) to solver instance 12 | fn push_formula(&mut self, formula: &str); 13 | 14 | /// Check if the pushed formula is satisfiable 15 | // this also allows mutable access since this is needed to communicate with the underlying 16 | // process in most cases 17 | fn check_sat(&mut self) -> bool; 18 | 19 | /// Check if the pushed formula is satisfiable and return a model for the given variable 20 | fn get_value(&mut self, value: &str) -> Option; 21 | 22 | /// Check if the pushed formula is satisfiable and return a model for the given variables 23 | fn get_values(&mut self, values: &[String]) -> Option>; 24 | 25 | /// Reset the solver instance 26 | fn reset(&mut self); 27 | } 28 | 29 | pub struct SolverHandle<'a> { 30 | worker: Option>, 31 | pool: &'a SolverPool, 32 | } 33 | 34 | impl<'a> SolverHandle<'a> { 35 | pub fn check_sat(&mut self) -> bool { 36 | self.as_mut().check_sat() 37 | } 38 | 39 | pub fn get_value(&mut self, value: &str) -> Option { 40 | self.as_mut().get_value(value) 41 | } 42 | pub fn get_values(&mut self, values: &[String]) -> Option> { 43 | self.as_mut().get_values(values) 44 | } 45 | 46 | // blocking call 47 | pub fn initialize_from_formel_builder(&mut self, builder: &SmtLib2Builder) { 48 | let worker = self.worker.as_mut().unwrap(); 49 | 50 | for def in builder.defs() { 51 | worker.push_formula(def); 52 | } 53 | for assert in builder.asserts() { 54 | worker.push_formula(assert); 55 | } 56 | } 57 | 58 | fn as_mut(&mut self) -> &mut Box { 59 | self.worker.as_mut().unwrap() 60 | } 61 | } 62 | 63 | // When droping the handle reset z3 and add it back to the pool 64 | impl<'a> Drop for SolverHandle<'a> { 65 | fn drop(&mut self) { 66 | let mut worker = self.worker.take().expect("Solver incorrectly closed"); 67 | worker.reset(); 68 | 69 | self.pool.add_worker(worker); 70 | } 71 | } 72 | 73 | pub struct SolverPool { 74 | queue: SegQueue>, 75 | } 76 | 77 | impl fmt::Debug for SolverPool { 78 | fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { 79 | Ok(()) 80 | } 81 | } 82 | 83 | pub enum Solvers { 84 | Initialized(Arc), 85 | Z3 { count: usize, timeout: usize }, 86 | Boolector { count: usize, timeout: usize }, 87 | Yice { count: usize, timeout: usize }, 88 | } 89 | 90 | pub fn create_pool(choice: Solvers) -> Arc { 91 | match choice { 92 | Solvers::Initialized(pool) => pool, 93 | Solvers::Z3 { count, timeout } => Arc::new(z3_pool_with_workers(count, timeout)), 94 | Solvers::Boolector { count, timeout } => { 95 | Arc::new(boolector_pool_with_workers(count, timeout)) 96 | } 97 | Solvers::Yice { count, timeout } => Arc::new(yice_pool_with_workers(count, timeout)), 98 | } 99 | } 100 | 101 | fn z3_pool_with_workers(count: usize, timeout: usize) -> SolverPool { 102 | let pool = SolverPool::new(); 103 | 104 | for _ in 0..count { 105 | let worker = Box::new(Z3Instance::new(timeout)); 106 | pool.add_worker(worker) 107 | } 108 | 109 | pool 110 | } 111 | 112 | fn boolector_pool_with_workers(count: usize, timeout: usize) -> SolverPool { 113 | let pool = SolverPool::new(); 114 | 115 | for _ in 0..count { 116 | let worker = Box::new(BoolectorInstance::new(timeout)); 117 | pool.add_worker(worker) 118 | } 119 | 120 | pool 121 | } 122 | 123 | fn yice_pool_with_workers(count: usize, timeout: usize) -> SolverPool { 124 | let pool = SolverPool::new(); 125 | 126 | for _ in 0..count { 127 | let worker = Box::new(YiceInstance::new(timeout)); 128 | pool.add_worker(worker) 129 | } 130 | 131 | pool 132 | } 133 | 134 | impl SolverPool { 135 | fn new() -> Self { 136 | let queue = SegQueue::new(); 137 | SolverPool { queue } 138 | } 139 | 140 | fn add_worker(&self, worker: Box) { 141 | self.queue.push(worker) 142 | } 143 | 144 | fn retrieve_worker(&self) -> Option> { 145 | return { 146 | let worker; 147 | loop { 148 | match self.queue.pop() { 149 | Some(w) => { 150 | worker = Some(w); 151 | break; 152 | } 153 | None => (), 154 | } 155 | } 156 | worker 157 | }; 158 | } 159 | 160 | pub fn initialize_from_formel_builder(&self, builder: &SmtLib2Builder) -> SolverHandle { 161 | let worker = self.retrieve_worker(); 162 | 163 | let mut handle = SolverHandle { 164 | worker: worker, 165 | pool: self, 166 | }; 167 | handle.initialize_from_formel_builder(builder); 168 | handle 169 | } 170 | 171 | // only available for testing 172 | #[cfg(test)] 173 | pub fn solver_handle(&self) -> SolverHandle { 174 | let worker = self.retrieve_worker(); 175 | 176 | let mut handle = SolverHandle { 177 | worker: worker, 178 | pool: self, 179 | }; 180 | handle 181 | } 182 | } 183 | 184 | #[cfg(test)] 185 | mod tests { 186 | use super::{yice_pool_with_workers, SolverPool}; 187 | 188 | const TEST_TIMEOUT: usize = 120; 189 | 190 | #[test] 191 | fn yice_solver_pool() { 192 | let f_false = String::from( 193 | "(declare-const a (_ BitVec 256)) 194 | (declare-const b (_ BitVec 256)) 195 | (assert (= a (_ bv10 256))) 196 | (assert (= b (_ bv11 256))) 197 | (assert (= a b))", 198 | ); 199 | 200 | let mut pool = yice_pool_with_workers(5, TEST_TIMEOUT); 201 | for i in 0..10 { 202 | let mut handle = pool.solver_handle(); 203 | { 204 | let mut yice = handle.worker.take().unwrap(); 205 | for line in f_false.lines() { 206 | yice.push_formula(line); 207 | } 208 | handle.worker.replace(yice); 209 | }; 210 | assert_eq!(false, handle.check_sat()); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /esvm/src/se/expr/yice.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::DefaultHasher, 3 | fs::File, 4 | hash::{Hash, Hasher}, 5 | io::Write, 6 | }; 7 | 8 | use regex::Regex; 9 | use subprocess::{Exec, Redirection}; 10 | use uint::U256; 11 | 12 | use crate::se::{ 13 | expr::{ 14 | bval::{const_u256, BVal}, 15 | solver::Solver, 16 | }, 17 | symbolic_analysis::CONFIG, 18 | }; 19 | 20 | lazy_static! { 21 | static ref VALUE_RE: Regex = 22 | // yice only supports binary encoding for return values 23 | Regex::new(r"\(\((?P(.*))\n?\s*#b(?P([01]*))\)\)").unwrap(); 24 | } 25 | 26 | pub struct YiceInstance { 27 | timeout: usize, 28 | input_buffer: String, 29 | dump: bool, 30 | } 31 | 32 | fn parse_binary_str(input: &str) -> U256 { 33 | let reverse: Vec = input.chars().collect(); 34 | debug_assert!(reverse.len() % 8 == 0); 35 | let mut result: [u8; 32] = [0; 32]; 36 | for (i, chunk) in reverse.as_slice().chunks(8).rev().enumerate() { 37 | let mut byte: u8 = 0; 38 | for (i, c) in chunk.iter().rev().enumerate() { 39 | match c { 40 | '0' => {} 41 | '1' => byte ^= 1 << i, 42 | _ => unreachable!(), 43 | } 44 | } 45 | result[31 - i] = byte; 46 | } 47 | result.into() 48 | } 49 | 50 | impl YiceInstance { 51 | pub fn new(timeout: usize) -> Self { 52 | let input_buffer = String::from("(set-logic QF_ABV)\n"); 53 | let timeout = timeout / 1_000; // yice uses seconds 54 | let dump = CONFIG.read().unwrap().dump_solver; 55 | Self { 56 | timeout, 57 | input_buffer, 58 | dump, 59 | } 60 | } 61 | 62 | fn communicate(&self) -> String { 63 | Exec::cmd("yices-smt2") 64 | .arg(format!("--timeout={}", self.timeout).as_str()) 65 | .stdin(self.input_buffer.as_str()) 66 | .stderr(Redirection::Merge) 67 | .stdout(Redirection::Pipe) 68 | .capture() 69 | .unwrap() 70 | .stdout_str() 71 | } 72 | 73 | fn check(&self, output: &str) -> bool { 74 | if output.contains("yice") { 75 | debug!("Yice error: {}, for input: {}", output, self.input_buffer); 76 | false 77 | } else if output.contains("unsat") { 78 | false 79 | } else if output.contains("sat") { 80 | true 81 | } else if output.contains("unknown") { 82 | warn!("Solver timed out after {} seconds", self.timeout); 83 | false 84 | } else { 85 | debug!( 86 | "Strange yice output: {}, for input: {}", 87 | output, self.input_buffer 88 | ); 89 | false 90 | } 91 | } 92 | } 93 | 94 | impl Solver for YiceInstance { 95 | fn push_formula(&mut self, formula: &str) { 96 | self.input_buffer.push_str(&format!("{}\n", formula)); 97 | } 98 | 99 | fn check_sat(&mut self) -> bool { 100 | self.input_buffer.push_str("(check-sat)\n"); 101 | self.check(&self.communicate()) 102 | } 103 | 104 | fn get_value(&mut self, value: &str) -> Option { 105 | self.input_buffer.push_str("(check-sat)\n"); 106 | self.input_buffer 107 | .push_str(&format!("(get-value ({}))\n", value)); 108 | let output = self.communicate(); 109 | 110 | if !self.check(&output) { 111 | return None; 112 | } 113 | 114 | let caps = if let Some(x) = VALUE_RE.captures(&output) { 115 | x 116 | } else { 117 | return None; 118 | }; 119 | caps.name("value") 120 | .map(|x| const_u256(parse_binary_str(x.as_str()))) 121 | } 122 | 123 | fn get_values(&mut self, values: &[String]) -> Option> { 124 | self.input_buffer.push_str("(check-sat)\n"); 125 | for value in values { 126 | self.input_buffer 127 | .push_str(&format!("(get-value ({}))\n", value)); 128 | } 129 | let output = self.communicate(); 130 | 131 | if !self.check(&output) { 132 | return None; 133 | } 134 | 135 | let mut result = Vec::with_capacity(values.len()); 136 | for cap in VALUE_RE.captures_iter(&output) { 137 | result.push( 138 | cap.name("value") 139 | .map(|x| const_u256(parse_binary_str(x.as_str())))?, 140 | ); 141 | } 142 | 143 | Some(result) 144 | } 145 | 146 | fn reset(&mut self) { 147 | if self.dump { 148 | let mut s = DefaultHasher::new(); 149 | self.input_buffer.hash(&mut s); 150 | let f_name = format!("queries/{:x}.smt2", s.finish()); 151 | let mut file = File::create(f_name).unwrap(); 152 | file.write_all(self.input_buffer.as_bytes()).unwrap(); 153 | } 154 | 155 | self.input_buffer.clear(); 156 | self.input_buffer.push_str("(set-logic QF_ABV)\n") 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | use crate::se::expr::bval::const_usize; 164 | 165 | const TEST_TIMEOUT: usize = 120; 166 | 167 | #[test] 168 | fn yice_general_test() { 169 | let test_formula = String::from( 170 | "(declare-const a (_ BitVec 256)) 171 | (declare-const b (_ BitVec 8)) 172 | (assert (= a (_ bv10 256))) 173 | (assert (= b ((_ extract 7 0) a) ))", 174 | ); 175 | let mut yice = YiceInstance::new(TEST_TIMEOUT); 176 | for line in test_formula.lines() { 177 | yice.push_formula(line); 178 | } 179 | assert_eq!(true, yice.check_sat()); 180 | } 181 | 182 | #[test] 183 | fn yice_general_false_test() { 184 | let f_false = String::from( 185 | "(declare-const a (_ BitVec 256)) 186 | (declare-const b (_ BitVec 256)) 187 | (assert (= a (_ bv10 256))) 188 | (assert (= b (_ bv11 256))) 189 | (assert (= a b))", 190 | ); 191 | let mut yice = YiceInstance::new(TEST_TIMEOUT); 192 | for line in f_false.lines() { 193 | yice.push_formula(line); 194 | } 195 | assert_eq!(false, yice.check_sat()); 196 | } 197 | 198 | #[test] 199 | fn yice_get_value() { 200 | let mut yice = YiceInstance::new(TEST_TIMEOUT); 201 | 202 | yice.push_formula(&String::from("(declare-const a (_ BitVec 256))")); 203 | yice.push_formula(&String::from("(assert (= a (_ bv10 256)))")); 204 | assert_eq!(Some(const_usize(10)), yice.get_value("a")); 205 | } 206 | 207 | #[test] 208 | fn yice_get_value_8() { 209 | let mut yice = YiceInstance::new(TEST_TIMEOUT); 210 | 211 | yice.push_formula(&String::from("(declare-const a (_ BitVec 8))")); 212 | yice.push_formula(&String::from("(assert (= a (_ bv10 8)))")); 213 | assert_eq!(Some(const_usize(10)), yice.get_value("a")); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /esvm/src/se/expr/z3.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::DefaultHasher, 3 | error::Error, 4 | fs::File, 5 | hash::{Hash, Hasher}, 6 | io::{prelude::*, BufReader, Write}, 7 | process::{Child, Command, Stdio}, 8 | sync::mpsc::{channel, Receiver}, 9 | thread, 10 | time::Duration, 11 | }; 12 | 13 | use regex::Regex; 14 | 15 | use crate::se::{ 16 | expr::{ 17 | bval::{const256, BVal}, 18 | solver::Solver, 19 | }, 20 | symbolic_analysis::CONFIG, 21 | }; 22 | 23 | lazy_static! { 24 | static ref GET_VALUE_RE: Regex = 25 | Regex::new(r"\(\((?P(.*)) \(_ bv(?P([0-9]*)) (256|8)\)\)\)").unwrap(); 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Z3Instance { 30 | process: Child, 31 | receiver: Receiver, 32 | current_input: String, 33 | timeout: usize, 34 | warnings: bool, 35 | dump: bool, 36 | 37 | values_as_dec: bool, 38 | } 39 | 40 | // dropping causes the process to stick around, since Child does not implement the drop trait 41 | // see: https://doc.rust-lang.org/std/process/struct.Child.html 42 | impl Drop for Z3Instance { 43 | fn drop(&mut self) { 44 | self.exit(); 45 | } 46 | } 47 | 48 | impl Z3Instance { 49 | pub fn new(timeout: usize) -> Self { 50 | let timeout = timeout * 1000; // z3 (soft) timeouts operate in milliseconds 51 | let mut child = match Command::new("z3") 52 | .args(&["-smt2", &format!("-t:{}", timeout), "-in"]) 53 | .stdin(Stdio::piped()) 54 | .stdout(Stdio::piped()) 55 | .stderr(Stdio::null()) 56 | .spawn() 57 | { 58 | Err(why) => panic!("couldn't spawn z3: {}", why.description()), 59 | Ok(process) => process, 60 | }; 61 | 62 | let (sender, receiver) = channel(); 63 | let reader = BufReader::new(child.stdout.take().unwrap()); 64 | 65 | // spawn receiver thread that constantly reads from the underlying process 66 | // when the process dies, child stdout gets droped which will drop reader which in turn drops the thread 67 | let _ = thread::spawn(move || { 68 | reader 69 | .lines() 70 | .filter_map(|line| line.ok()) 71 | .for_each(|line| match sender.send(line) { 72 | Ok(_) => {} 73 | Err(_) => { 74 | return; 75 | } 76 | }); 77 | }); 78 | 79 | let values_as_dec = false; 80 | let current_input = String::new(); 81 | let dump = CONFIG.read().unwrap().dump_solver; 82 | let mut z3 = Z3Instance { 83 | process: child, 84 | receiver, 85 | values_as_dec, 86 | current_input, 87 | timeout, 88 | dump, 89 | warnings: true, 90 | }; 91 | z3.set_config(); 92 | z3 93 | } 94 | } 95 | impl Solver for Z3Instance { 96 | fn push_formula(&mut self, formula: &str) { 97 | self.current_input.push_str(&format!("{}\n", formula)); 98 | self.send(formula); 99 | } 100 | 101 | fn check_sat(&mut self) -> bool { 102 | self.check() 103 | } 104 | 105 | fn reset(&mut self) { 106 | self.send("(reset)"); 107 | self.current_input.clear(); 108 | } 109 | 110 | fn get_value(&mut self, value: &str) -> Option { 111 | self.values_as_dec(); 112 | if !self.check() { 113 | return None; 114 | } 115 | 116 | self.send(&format!("(get-value ({}))", value)); 117 | let res = self.recv().unwrap(); 118 | let caps = if let Some(x) = GET_VALUE_RE.captures(&res) { 119 | x 120 | } else { 121 | return None; 122 | }; 123 | caps.name("value").map(|x| const256(x.as_str())) 124 | } 125 | 126 | fn get_values(&mut self, values: &[String]) -> Option> { 127 | self.values_as_dec(); 128 | 129 | if !self.check() { 130 | return None; 131 | } 132 | 133 | let mut vals = Vec::with_capacity(values.len()); 134 | for value in values { 135 | self.send(&format!("(get-value ({}))", value)); 136 | let res = self.recv().unwrap(); 137 | let caps = if let Some(x) = GET_VALUE_RE.captures(&res) { 138 | x 139 | } else { 140 | return None; 141 | }; 142 | vals.push(caps.name("value").map(|x| const256(x.as_str()))?); 143 | } 144 | Some(vals) 145 | } 146 | } 147 | 148 | impl Z3Instance { 149 | fn set_config(&mut self) { 150 | self.send("(set-logic QF_ABV)"); 151 | self.send("(set-option :global-decls false)"); 152 | } 153 | 154 | // this will reset z3 since we can not control asserts/defs 155 | #[cfg(test)] 156 | fn check_formula(&mut self, formula: &str) -> bool { 157 | self.reset(); 158 | self.current_input.push_str(formula); 159 | self.send(formula); 160 | let res = self.check(); 161 | self.reset(); 162 | res 163 | } 164 | 165 | fn values_as_dec(&mut self) { 166 | if self.values_as_dec { 167 | return; 168 | } 169 | self.send("(set-option :pp.bv-literals false)"); 170 | self.values_as_dec = true; 171 | } 172 | 173 | fn send(&mut self, cmd: &str) { 174 | let process_in = self.process.stdin.as_mut().unwrap(); 175 | 176 | let mut input = String::from(cmd); 177 | input.push_str("\n"); 178 | process_in 179 | .write_all(input.as_bytes()) 180 | .expect("Could not write to z3 process"); 181 | process_in.flush().expect("Could not flush buffer"); 182 | } 183 | 184 | fn exit(&mut self) { 185 | self.send("(exit)"); 186 | self.process.wait().expect("Could not close z3 process"); 187 | } 188 | 189 | fn recv(&mut self) -> Result { 190 | let mut res = String::new(); 191 | 192 | // provide an initial timeout slightly longer then the query timeout 193 | let mut timeout = self.timeout + 200; 194 | let mut first_round = true; 195 | while let Ok(line) = self 196 | .receiver 197 | .recv_timeout(Duration::from_millis(timeout as u64)) 198 | { 199 | res.push_str(&line.replace("\n", "")); 200 | 201 | // after first round reset so we do not wait forever when we read all the output 202 | if first_round { 203 | timeout = 100; 204 | first_round = false; 205 | } 206 | } 207 | if res.contains("error") { 208 | return Err(res); 209 | } 210 | Ok(res) 211 | } 212 | 213 | // internal use 214 | fn check(&mut self) -> bool { 215 | self.send("(check-sat)"); 216 | if self.dump { 217 | let mut s = DefaultHasher::new(); 218 | self.current_input.hash(&mut s); 219 | let f_name = format!("queries/{:x}.smt2", s.finish()); 220 | let mut file = File::create(f_name).unwrap(); 221 | file.write_all(self.current_input.as_bytes()).unwrap(); 222 | } 223 | let res = match self.recv() { 224 | Ok(r) => r, 225 | Err(err) => { 226 | debug!( 227 | "Error in z3 output: {}\nInput: \n{}", 228 | err, 229 | self.debug_ouput() 230 | ); 231 | return false; 232 | } 233 | }; 234 | // solver timeout handeled as unsat 235 | if res.contains("unsat") { 236 | return false; 237 | } 238 | if res.contains("sat") { 239 | return true; 240 | } 241 | if res.contains("unknown") { 242 | if self.warnings { 243 | // timeout is specified in miliseconds 244 | warn!("Solver timed out after {} seconds.", self.timeout / 1_000); 245 | debug!("{}", self.debug_ouput()); 246 | } 247 | return false; 248 | } 249 | debug!("Strange z3 output: {}", res); 250 | false 251 | } 252 | 253 | fn debug_ouput(&self) -> String { 254 | let mut debug_ouput = String::new(); 255 | 256 | for (i, line) in self.current_input.lines().enumerate() { 257 | debug_ouput.push_str(&format!("{}\t{}\n", i, line)); 258 | } 259 | debug_ouput 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /esvm/src/se/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod env; 3 | #[macro_use] 4 | pub mod expr; 5 | pub mod symbolic_analysis; 6 | pub mod symbolic_edge; 7 | pub mod symbolic_executor; 8 | pub mod symbolic_graph; 9 | pub mod symbolic_state; 10 | -------------------------------------------------------------------------------- /esvm/src/se/symbolic_edge.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::Write; 3 | 4 | use crate::se::symbolic_state::escape_special_chars; 5 | 6 | use crate::se::expr::bval::BVal; 7 | 8 | #[derive(Debug, Clone)] 9 | pub enum EdgeType { 10 | Exec, 11 | CallRet, 12 | Cond(BVal), 13 | Terminal, 14 | Unsat, 15 | } 16 | 17 | pub fn edge_exec() -> EdgeType { 18 | EdgeType::Exec 19 | } 20 | 21 | pub fn edge_call_ret() -> EdgeType { 22 | EdgeType::CallRet 23 | } 24 | 25 | pub fn edge_terminal() -> EdgeType { 26 | EdgeType::Terminal 27 | } 28 | 29 | pub fn edge_conditional(val: BVal) -> EdgeType { 30 | EdgeType::Cond(val) 31 | } 32 | 33 | #[derive(Debug, Clone)] 34 | pub struct SymbolicEdge { 35 | pub from: usize, 36 | pub to: usize, 37 | pub etype: EdgeType, 38 | } 39 | 40 | impl SymbolicEdge { 41 | pub fn new(from: usize, to: usize, etype: EdgeType) -> Self { 42 | SymbolicEdge { from, to, etype } 43 | } 44 | 45 | pub fn to_dot(&self, w: &mut W) -> Result<(), io::Error> { 46 | let color = match self.etype { 47 | EdgeType::Exec => "limegreen", 48 | EdgeType::CallRet => "blue", 49 | EdgeType::Cond(_) => "orange", 50 | EdgeType::Terminal => "crimson", 51 | EdgeType::Unsat => "red", 52 | }; 53 | writeln!(w, "{} -> {}[color=\"{}\"]", self.from, self.to, color)?; 54 | Ok(()) 55 | } 56 | 57 | #[allow(dead_code)] 58 | pub fn to_dot_with_cond(&self, w: &mut W) -> Result<(), io::Error> { 59 | let color = match self.etype { 60 | EdgeType::Exec => "limegreen", 61 | EdgeType::CallRet => "blue", 62 | EdgeType::Cond(_) => "orange", 63 | EdgeType::Terminal => "crimson", 64 | EdgeType::Unsat => "red", 65 | }; 66 | match self.etype { 67 | EdgeType::Cond(ref cond) => { 68 | writeln!( 69 | w, 70 | "{} -> {}[color=\"{}\" label=\"{}\"]", 71 | self.from, 72 | self.to, 73 | color, 74 | escape_special_chars(format!("{:?}", cond), 300) 75 | )?; 76 | } 77 | _ => { 78 | writeln!(w, "{} -> {}[color=\"{}\"]", self.from, self.to, color)?; 79 | } 80 | } 81 | Ok(()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /esvm/src/se/symbolic_executor/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::bytecode::Instr; 2 | use crate::se::{ 3 | expr::bval::*, 4 | symbolic_edge::*, 5 | symbolic_executor::{call_ops::*, memory_ops::*, stack_ops::*}, 6 | symbolic_state::{Flags, SeState}, 7 | }; 8 | 9 | fn fail_unknown(i: &Instr) -> Vec<(SeState, EdgeType)> { 10 | warn!("encountered unknown instruction {:?}, droping path", i); 11 | vec![] 12 | } 13 | 14 | pub fn expensive_computation(s: &SeState) -> bool { 15 | let ins = s.get_instruction(); 16 | if ins.is_none() { 17 | return true; 18 | } 19 | match ins.unwrap() { 20 | Instr::IExtCodeSize 21 | | Instr::IBalance 22 | | Instr::ISelfDestruct 23 | | Instr::ICall 24 | | Instr::IStaticCall 25 | | Instr::ICallCode 26 | | Instr::IDelegateCall => true, 27 | _ => false, 28 | } 29 | } 30 | 31 | pub fn symbolic_step(s: &SeState) -> Vec<(SeState, EdgeType)> { 32 | let ins = s.get_instruction(); 33 | if ins.is_none() { 34 | error!("Could not fetch next instruction: {:?}", s); 35 | return vec![]; 36 | } 37 | let ins = ins.unwrap(); 38 | match ins { 39 | Instr::IAdd => arith2(s, |a, b| add(a, b)), 40 | Instr::ISub => arith2(s, |a, b| sub(a, b)), 41 | Instr::IMul => arith2(s, |a, b| mul(a, b)), 42 | Instr::IDiv => arith2(s, |a, b| div(a, b)), 43 | Instr::ISDiv => arith2(s, |a, b| sdiv(a, b)), 44 | Instr::IMod => arith2(s, |a, b| umod(a, b)), 45 | Instr::ISMod => arith2(s, |a, b| smod(a, b)), 46 | Instr::IAddMod => arith3(s, |a, b, c| umod(&add(a, b), c)), 47 | Instr::IMulMod => arith3(s, |a, b, c| umod(&mul(a, b), c)), 48 | Instr::IExp => exponentiation(s), 49 | Instr::ILt => arith2(s, |a, b| lt(a, b)), 50 | Instr::IGt => arith2(s, |a, b| lt(b, a)), 51 | Instr::ISLt => arith2(s, |a, b| slt(a, b)), 52 | Instr::ISGt => arith2(s, |a, b| slt(b, a)), 53 | Instr::IEql => arith2(s, |a, b| eql(a, b)), 54 | Instr::IIsZero => arith1(s, |a| eql(a, &zero())), 55 | Instr::IAnd => arith2(s, |a, b| and(a, b)), 56 | Instr::IOr => arith2(s, |a, b| or(a, b)), 57 | Instr::IXor => arith2(s, |a, b| xor(a, b)), 58 | Instr::INot => arith1(s, |a| not(a)), 59 | Instr::IByte => arith2(s, |offset, val| { 60 | ite(<(offset, &const256("32")), &byte_at(val, offset), &zero()) 61 | }), 62 | Instr::IShl => arith2(s, |shift, value| shl(value, shift)), 63 | Instr::IAShr => arith2(s, |shift, value| ashr(value, shift)), 64 | Instr::ILShr => arith2(s, |shift, value| lshr(value, shift)), 65 | Instr::ISHA3 => keccak(s), 66 | Instr::IAddr => arith0(s, Some(&s.account().addr)), 67 | Instr::IBalance => balance(s), 68 | Instr::IOrigin => arith0(s, Some(&s.input_tx().origin)), 69 | Instr::ICaller => arith0(s, Some(&s.input_tx().caller)), 70 | Instr::ICallValue => arith0(s, Some(&s.input_tx().callvalue)), 71 | Instr::ICallDataLoad => calldataload(s), 72 | Instr::ICallDataSize => arith0(s, Some(&s.input_tx().calldata_size)), 73 | Instr::ICallDataCopy => calldatacopy(s), 74 | Instr::ICodeSize => arith0(s, Some(&const_usize(s.get_codesize()))), 75 | Instr::IGasPrice => arith0(s, Some(&s.env.latest_block().gasprice)), 76 | Instr::IBlockHash => blockhash(s), 77 | Instr::ICoinBase => arith0(s, Some(&s.env.latest_block().coinbase)), 78 | Instr::ITimeStamp => arith0(s, Some(&s.env.latest_block().timestamp)), 79 | Instr::INumber => arith0(s, Some(&s.env.latest_block().number)), 80 | Instr::IDifficulty => arith0(s, Some(&s.env.latest_block().difficulty)), 81 | Instr::IGasLimit => arith0(s, Some(&s.env.latest_block().gas_limit)), 82 | Instr::IPop => pop_n(s, 1), 83 | Instr::IMLoad => memload(s), 84 | Instr::IMStore => mstore(s), 85 | Instr::IMStore8 => mstore8(s), 86 | Instr::ISLoad => storage_load(s), 87 | Instr::ISStore => sstore(s), 88 | Instr::IJump => jump(s), 89 | Instr::IJumpIf => jump_if(s), 90 | Instr::IPC => arith0(s, Some(&const_usize(s.pc))), 91 | Instr::IMSize => arith0(s, Some(&s.env.latest_block().mem_size)), 92 | Instr::IGas => arith0(s, Some(&s.input_tx().gas)), 93 | Instr::IJumpDest => vec![(s.create_succ(), edge_exec())], 94 | Instr::IPush(a) => arith0(s, Some(&const_vec(&a))), 95 | Instr::IDup(a) => arith0(s, s.stack.get(s.stack.len() - a)), 96 | Instr::ISwap(a) => swap(s, a), 97 | Instr::ILog(n) => { 98 | if s.flags.contains(Flags::STATIC) { 99 | warn!("State changing log operation during static call, dropping path!"); 100 | return vec![]; 101 | } 102 | pop_n(s, n + 2) 103 | } 104 | Instr::ICall => new_call(s, CallType::Call), 105 | Instr::IStaticCall => new_call(s, CallType::StaticCall), 106 | Instr::ICallCode => new_call(s, CallType::CallCode), 107 | Instr::IDelegateCall => new_call(s, CallType::DelegateCall), 108 | Instr::ISelfDestruct => selfdestruct(s), 109 | Instr::IStop => stop(s), 110 | Instr::IRevert => revert(s), 111 | Instr::IReturn => ireturn(s), 112 | Instr::IInvalid => vec![], // execution reached invalid state, drop path 113 | Instr::ICodeCopy => code_copy(s), 114 | Instr::IRDataSize => returndata_size(s), 115 | Instr::IRDataCopy => returndata_copy(s), 116 | Instr::IExtCodeSize => extcode_size(s), 117 | Instr::ISext => sign_extend(s), 118 | Instr::IExtCodeCopy => ext_code_copy(s), 119 | Instr::ICreate => create_account(s), 120 | Instr::ICreate2 => fail_unknown(&ins), 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /esvm/src/se/symbolic_executor/mod.rs: -------------------------------------------------------------------------------- 1 | mod call_ops; 2 | mod executor; 3 | mod memory_ops; 4 | mod stack_ops; 5 | 6 | pub use self::executor::*; 7 | -------------------------------------------------------------------------------- /esvm/src/se/symbolic_executor/stack_ops.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::se::{ 4 | expr::bval::*, 5 | symbolic_edge::{edge_exec, edge_terminal, EdgeType}, 6 | symbolic_state::{HaltingReason, SeState}, 7 | }; 8 | 9 | pub fn stop(s: &SeState) -> Vec<(SeState, EdgeType)> { 10 | let mut res = s.create_succ(); 11 | res.reset_returndata(); 12 | res.halting_reason = Some(HaltingReason::Stop); 13 | vec![(res, edge_terminal())] 14 | } 15 | 16 | pub fn pop_n(s: &SeState, mut n: usize) -> Vec<(SeState, EdgeType)> { 17 | let mut res = s.create_succ(); 18 | while n > 0 { 19 | if res.stack.pop().is_none() { 20 | return vec![]; 21 | } 22 | n -= 1; 23 | } 24 | vec![(res, edge_exec())] 25 | } 26 | 27 | pub fn swap(s: &SeState, n: usize) -> Vec<(SeState, EdgeType)> { 28 | let mut res = s.create_succ(); 29 | let last = res.stack.len() - 1; 30 | if let Some(low) = res.stack.get(last - n).cloned() { 31 | let high = res.stack[last].clone(); 32 | res.stack[last] = low.clone(); 33 | res.stack[last - n] = high; 34 | return vec![(res, edge_exec())]; 35 | } 36 | vec![] 37 | } 38 | 39 | pub fn blockhash(s: &SeState) -> Vec<(SeState, EdgeType)> { 40 | let mut res = s.create_succ(); 41 | if let Some(blocknumber) = res.stack.pop() { 42 | match ( 43 | &res.env.blockhashes, 44 | FVal::as_usize(&blocknumber), 45 | FVal::as_usize(&res.env.latest_block().number), 46 | ) { 47 | (Some(hashes), Some(number), Some(current_blocknumber)) => { 48 | // check if future number is requested 49 | if number > current_blocknumber { 50 | warn!("Accessing future blocknumber: {}!", number); 51 | res.push(zero()); 52 | } else { 53 | let difference = current_blocknumber - number; 54 | if let Some(hash) = hashes.get(difference) { 55 | res.push(Arc::clone(hash)); 56 | } else { 57 | warn!("Accessing invalid blockhash: {}!", difference); 58 | res.push(zero()); 59 | } 60 | } 61 | } 62 | (Some(hashes), None, _) => { 63 | // symbolic number ite over all possible cases 64 | let mut iter = hashes.iter(); 65 | let first = iter.next().unwrap(); 66 | let mut ifthen = ite( 67 | // condition 68 | &eql( 69 | &blocknumber, 70 | &sub(&Arc::clone(&res.env.latest_block().number), &const_usize(0)), 71 | ), 72 | // true 73 | &first, 74 | // false 75 | &const_usize(0), 76 | ); 77 | 78 | // start iterating +1 79 | for (i, hash) in iter.enumerate() { 80 | ifthen = ite( 81 | // condition 82 | &eql( 83 | &blocknumber, 84 | &sub( 85 | &Arc::clone(&res.env.latest_block().number), 86 | &const_usize(i + 1), 87 | ), 88 | ), 89 | // true 90 | &hash, 91 | // false 92 | &ifthen, 93 | ); 94 | } 95 | res.push(ifthen); 96 | } 97 | _ => { 98 | // overapproximate with latest block hash 99 | res.push(Arc::clone(&s.env.latest_block().blockhash)); 100 | } 101 | } 102 | 103 | return vec![(res, edge_exec())]; 104 | } 105 | vec![] 106 | } 107 | 108 | // Only support constant atm 109 | pub fn exponentiation(s: &SeState) -> Vec<(SeState, EdgeType)> { 110 | let mut res = s.create_succ(); 111 | if let Some((base, exponent)) = res.pop2() { 112 | match (FVal::as_usize(&base), FVal::as_usize(&exponent)) { 113 | (Some(_), Some(_)) | (Some(2), None) => { 114 | res.push(exp(&base, &exponent)); 115 | return vec![(res, edge_exec())]; 116 | } 117 | _ => warn!("Not supported symbolic exponentiation, dropping path"), 118 | } 119 | } 120 | vec![] 121 | } 122 | 123 | pub fn sign_extend(s: &SeState) -> Vec<(SeState, EdgeType)> { 124 | let mut res = s.create_succ(); 125 | if let Some((size, value)) = res.pop2() { 126 | let testbit = ite( 127 | &le(&size, &const_usize(31)), 128 | &add(&mul(&size, &const_usize(8)), &const_usize(7)), 129 | &const_usize(257), 130 | ); 131 | let res_1 = or( 132 | &value, 133 | &sub(&const_usize(2 ^ 256), &shl(&const_usize(1), &testbit)), 134 | ); 135 | let res_2 = and( 136 | &value, 137 | &sub(&shl(&const_usize(1), &testbit), &const_usize(1)), 138 | ); 139 | let final_res = ite( 140 | &neql( 141 | &and(&value, &shl(&const_usize(1), &testbit)), 142 | &const_usize(0), 143 | ), 144 | &res_1, 145 | &res_2, 146 | ); 147 | res.push(final_res); 148 | return vec![(res, edge_exec())]; 149 | } 150 | vec![] 151 | } 152 | 153 | pub fn jump_if(s: &SeState) -> Vec<(SeState, EdgeType)> { 154 | let mut fallthrough = s.create_succ(); 155 | if let Some((ref target, ref cond)) = fallthrough.pop2() { 156 | let targets_iter = fallthrough.jump_to(target).into_iter(); 157 | let mut targets = targets_iter 158 | .flat_map(|t| s.get_jump_info(cond, target, t)) 159 | .collect::>(); 160 | let fallthrough_cond = eql(cond, &zero()); 161 | if let Some(ft) = 162 | s.get_jump_info(&fallthrough_cond, &const_usize(fallthrough.pc), fallthrough) 163 | { 164 | targets.push(ft); 165 | } 166 | return targets; 167 | } 168 | vec![] 169 | } 170 | 171 | pub fn jump(s: &SeState) -> Vec<(SeState, EdgeType)> { 172 | let mut res = s.create_succ(); 173 | if let Some(ref target) = res.stack.pop() { 174 | return res 175 | .jump_to(target) 176 | .into_iter() 177 | .flat_map(|t| s.get_jump_info(&one(), target, t)) 178 | .collect::>(); 179 | } 180 | vec![] 181 | } 182 | 183 | pub fn arith0(s: &SeState, a: Option<&BVal>) -> Vec<(SeState, EdgeType)> { 184 | if let Some(v) = a { 185 | let mut res = s.create_succ(); 186 | res.push(v.clone()); 187 | vec![(res, edge_exec())] 188 | } else { 189 | vec![] 190 | } 191 | } 192 | 193 | pub fn arith1(s: &SeState, f: F) -> Vec<(SeState, EdgeType)> 194 | where 195 | F: Fn(&BVal) -> BVal, 196 | { 197 | let mut res = s.create_succ(); 198 | if let Some(ref a) = res.stack.pop() { 199 | res.push(f(a)); 200 | return vec![(res, edge_exec())]; 201 | } 202 | vec![] 203 | } 204 | 205 | pub fn arith2(s: &SeState, f: F) -> Vec<(SeState, EdgeType)> 206 | where 207 | F: Fn(&BVal, &BVal) -> BVal, 208 | { 209 | let mut res = s.create_succ(); 210 | if let Some((ref a, ref b)) = res.pop2() { 211 | res.push(f(a, b)); 212 | return vec![(res, edge_exec())]; 213 | } 214 | vec![] 215 | } 216 | 217 | pub fn arith3(s: &SeState, f: F) -> Vec<(SeState, EdgeType)> 218 | where 219 | F: Fn(&BVal, &BVal, &BVal) -> BVal, 220 | { 221 | let mut res = s.create_succ(); 222 | if let Some((ref a, ref b, ref c)) = res.pop3() { 223 | res.push(f(a, b, c)); 224 | return vec![(res, edge_exec())]; 225 | } 226 | vec![] 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use super::*; 232 | 233 | use crate::bytecode::Instr; 234 | use crate::test_helpers::generate_test_graph; 235 | 236 | #[test] 237 | fn swap() { 238 | let ins = vec![ 239 | Instr::IPush(vec![0xfa, 0x11, 0xfa, 0x22]), //value 240 | Instr::IPush(vec![0x42, 0x43, 0x44, 0x45]), //value 241 | Instr::IPush(vec![0xaa, 0xbb, 0xcc, 0xdd]), //value 242 | Instr::ISwap(2), 243 | ]; 244 | 245 | let g = generate_test_graph(ins); 246 | 247 | let state = &g.get_state_by_id(5); 248 | assert_eq!(0xaabbccdd, FVal::as_usize(&state.stack[0]).unwrap()); 249 | assert_eq!(0xfa11fa22, FVal::as_usize(&state.stack[2]).unwrap()); 250 | } 251 | 252 | // just check for no panic 253 | #[test] 254 | fn jump_at_end() { 255 | let ins = vec![ 256 | Instr::IPush(vec![0x5]), 257 | Instr::IJump, 258 | Instr::IJumpDest, 259 | Instr::IStop, 260 | Instr::IJumpDest, 261 | Instr::IPush(vec![0x3]), 262 | Instr::IJump, 263 | ]; 264 | let _g = generate_test_graph(ins); 265 | assert!(true); 266 | } 267 | 268 | #[test] 269 | fn byte_at_test() { 270 | let ins = vec![ 271 | Instr::IPush(vec![0x41, 0x41, 0x41, 0x41]), // out size 272 | Instr::IPush(vec![0x00]), // out offset 273 | Instr::IByte, 274 | ]; 275 | let g = generate_test_graph(ins); 276 | 277 | let state = &g.get_state_by_id(4); 278 | assert_eq!(FVal::as_usize(&state.stack[0]).unwrap(), 0x41); 279 | } 280 | 281 | #[test] 282 | fn dup_test() { 283 | let ins = vec![ 284 | Instr::IPush(vec![0x01]), 285 | Instr::IPush(vec![0x02]), 286 | Instr::IPush(vec![0x03]), 287 | Instr::IDup(1), 288 | Instr::IDup(4), 289 | ]; 290 | 291 | let g = generate_test_graph(ins); 292 | let state = &g.get_state_by_id(5); 293 | assert_eq!(const_usize(0x03), state.stack[3]); 294 | let state = &g.get_state_by_id(6); 295 | assert_eq!(const_usize(0x01), state.stack[4]); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /esvm/src/se/symbolic_graph.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::Write; 3 | use std::sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | Arc, 6 | }; 7 | use std::thread::{self, JoinHandle}; 8 | 9 | use crossbeam::queue::SegQueue; 10 | use crossbeam_channel as channel; 11 | 12 | use crate::se::symbolic_edge::{EdgeType, SymbolicEdge}; 13 | use crate::se::symbolic_executor::{self, symbolic_step}; 14 | use crate::se::symbolic_state::SeState; 15 | 16 | pub struct SymbolicGraph { 17 | states: Vec>, 18 | edges: Vec, 19 | initial_state: Arc, 20 | end_states: Vec>, 21 | 22 | unprocessed_states: Arc>>, 23 | being_processed: Arc, 24 | 25 | // channels for communicating with worker threads 26 | transition_input: Channels>, 27 | transition_output: Channels, 28 | 29 | solver_needed_input: Channels, 30 | solver_needed_output: Channels, 31 | 32 | solver_not_needed: Channels<(usize, Vec<(SeState, EdgeType)>)>, 33 | 34 | kill_switch_sender: Option>, // only used for ending all threads 35 | kill_switch_receiver: channel::Receiver<()>, // only used for ending all threads 36 | 37 | // Worker threads 38 | transition_workers: Vec, 39 | solver_needed_workers: Vec, 40 | } 41 | 42 | struct TransitionWorker { 43 | handle: Option>, 44 | } 45 | 46 | impl TransitionWorker { 47 | fn new( 48 | input: channel::Receiver>, 49 | output: channel::Sender, 50 | kill_switch: channel::Receiver<()>, 51 | ) -> Self { 52 | let handle = Some(thread::spawn(move || { 53 | loop { 54 | select! { 55 | recv(input) -> msg => { 56 | let input_state = msg.unwrap(); // channel does not close 57 | let transitions = symbolic_step(&input_state); 58 | let res = Transition::new(input_state.id, transitions); 59 | output.send(res); 60 | }, 61 | // closing signal, shut down worker 62 | recv(kill_switch) -> msg => { 63 | debug_assert!(msg.is_err()); 64 | break; 65 | } 66 | } 67 | } 68 | })); 69 | Self { handle } 70 | } 71 | } 72 | 73 | struct SolverWorker { 74 | handle: Option>, 75 | } 76 | 77 | impl SolverWorker { 78 | fn new( 79 | input: channel::Receiver, 80 | output: channel::Sender, 81 | kill_switch: channel::Receiver<()>, 82 | ) -> Self { 83 | let handle = Some(thread::spawn(move || { 84 | loop { 85 | select! { 86 | recv(input) -> msg => { 87 | let input = msg.unwrap(); // channel does not close 88 | let mut res = Vec::with_capacity(input.len()); 89 | let len = input.len(); 90 | for (state, edge) in input.transitions { 91 | let check = if len == 1 { 92 | true 93 | } else { 94 | state.check_sat() 95 | }; 96 | res.push( ((state, edge), check) ); 97 | } 98 | output.send(SolverOutput{ id: input.id, transitions: res}); 99 | }, 100 | // closing signal, shut down worker 101 | recv(kill_switch) -> msg => { 102 | debug_assert!(msg.is_err()); 103 | break; 104 | } 105 | } 106 | } 107 | })); 108 | Self { handle } 109 | } 110 | } 111 | 112 | struct SolverInput { 113 | // the id of the input state corresponding to the transitions 114 | id: usize, 115 | transitions: Vec<(SeState, EdgeType)>, 116 | } 117 | 118 | impl SolverInput { 119 | fn len(&self) -> usize { 120 | self.transitions.len() 121 | } 122 | } 123 | 124 | struct SolverOutput { 125 | id: usize, 126 | transitions: Vec<((SeState, EdgeType), bool)>, 127 | } 128 | 129 | struct Channels { 130 | sender: channel::Sender, 131 | receiver: channel::Receiver, 132 | } 133 | 134 | impl Channels { 135 | fn new_bounded(bound: usize) -> Self { 136 | let (sender, receiver) = channel::bounded(bound); 137 | Self { sender, receiver } 138 | } 139 | 140 | fn new_unbounded() -> Self { 141 | let (sender, receiver) = channel::unbounded(); 142 | Self { sender, receiver } 143 | } 144 | } 145 | 146 | struct Transition { 147 | // the id of the input state corresponding to the transitions 148 | id: usize, 149 | transitions: Vec<(SeState, EdgeType)>, 150 | } 151 | 152 | impl Transition { 153 | fn new(id: usize, transitions: Vec<(SeState, EdgeType)>) -> Self { 154 | Self { id, transitions } 155 | } 156 | } 157 | 158 | // since the select macro borrows a channel from the graph immutably, we can not call a mutable 159 | // function from the select macros, thus we simply inline the handle_transitions function 160 | // passing self as an identifier is required see: 161 | // https://stackoverflow.com/questions/44120455/how-to-call-methods-on-self-in-macros 162 | // https://danielkeep.github.io/tlborm/book/mbe-min-non-identifier-identifiers.html 163 | macro_rules! handle_transitions { 164 | ($self:ident, $next_id:ident, $transitions:ident) => { 165 | // splitt vec into two 166 | // use vec.drain_filter once available to reuse memory 167 | let (conditional, simple): (Vec<_>, Vec<_>) = 168 | $transitions.into_iter().partition(|(_, edge_info)| { 169 | if let EdgeType::Cond(_) = edge_info { 170 | true 171 | } else { 172 | false 173 | } 174 | }); 175 | 176 | // handle easy cases first 177 | if !simple.is_empty() { 178 | $self.solver_not_needed.sender.send(($next_id, simple)); 179 | $self.being_processed.fetch_add(1, Ordering::SeqCst); 180 | } 181 | 182 | if !conditional.is_empty() { 183 | // send more complex state transitions to be processed async 184 | $self.solver_needed_input.sender.send(SolverInput { 185 | id: $next_id, 186 | transitions: conditional, 187 | }); 188 | $self.being_processed.fetch_add(1, Ordering::SeqCst); 189 | } 190 | }; 191 | } 192 | 193 | // cleanup worker threads 194 | impl Drop for SymbolicGraph { 195 | fn drop(&mut self) { 196 | // close all workers when we don't execute the graph 197 | // mainly for testing 198 | if self.kill_switch_sender.is_some() { 199 | let kill_switch = self 200 | .kill_switch_sender 201 | .take() 202 | .expect("Could not take kill switch"); 203 | drop(kill_switch); // closes kill switch channel 204 | } 205 | for worker in &mut self.transition_workers { 206 | worker 207 | .handle 208 | .take() 209 | .expect("Handle not set correctly on worker thread") 210 | .join() 211 | .expect("Threads not closed properly after analysis"); 212 | } 213 | for worker in &mut self.solver_needed_workers { 214 | worker 215 | .handle 216 | .take() 217 | .expect("Handle not set correctly on worker thread") 218 | .join() 219 | .expect("Threads not closed properly after analysis"); 220 | } 221 | } 222 | } 223 | 224 | impl SymbolicGraph { 225 | pub fn new(initial_state: SeState) -> Self { 226 | let initial_state = Arc::new(initial_state); 227 | let states = vec![Arc::clone(&initial_state)]; 228 | let edges = vec![]; 229 | let end_states = vec![]; 230 | 231 | let unprocessed_states = Arc::new(SegQueue::new()); 232 | unprocessed_states.push(Arc::clone(&initial_state)); 233 | let being_processed = Arc::new(AtomicUsize::new(0)); 234 | 235 | // + 1 for uneven number of cores, they block on solver anyways 236 | let cores = (initial_state.config().cores / 2) + 1; 237 | 238 | // create channels 239 | let transition_input = Channels::new_unbounded(); 240 | let transition_output = Channels::new_unbounded(); 241 | 242 | let solver_needed_input = Channels::new_unbounded(); 243 | let solver_needed_output = Channels::new_unbounded(); 244 | 245 | let solver_not_needed = Channels::new_unbounded(); 246 | 247 | let kill_switch = Channels::new_bounded(0); 248 | let kill_switch_sender = Some(kill_switch.sender); 249 | let kill_switch_receiver = kill_switch.receiver; 250 | 251 | // create worker threads 252 | let mut transition_workers = Vec::with_capacity(cores); 253 | for _ in 0..cores { 254 | let (input, output, kill_switch) = ( 255 | transition_input.receiver.clone(), 256 | transition_output.sender.clone(), 257 | kill_switch_receiver.clone(), 258 | ); 259 | transition_workers.push(TransitionWorker::new(input, output, kill_switch)); 260 | } 261 | 262 | let mut solver_needed_workers = Vec::with_capacity(cores); 263 | for _ in 0..cores { 264 | let (input, output, kill_switch) = ( 265 | solver_needed_input.receiver.clone(), 266 | solver_needed_output.sender.clone(), 267 | kill_switch_receiver.clone(), 268 | ); 269 | solver_needed_workers.push(SolverWorker::new(input, output, kill_switch)); 270 | } 271 | 272 | SymbolicGraph { 273 | unprocessed_states, 274 | initial_state, 275 | states, 276 | edges, 277 | end_states, 278 | transition_input, 279 | transition_output, 280 | solver_needed_input, 281 | solver_needed_output, 282 | solver_not_needed, 283 | kill_switch_sender, 284 | kill_switch_receiver, 285 | transition_workers, 286 | solver_needed_workers, 287 | being_processed, 288 | } 289 | } 290 | 291 | pub fn analyze_graph(&mut self) { 292 | let kill_switch_sender = self.kill_switch_sender.take().unwrap(); 293 | let unprocessed_states = Arc::clone(&self.unprocessed_states); 294 | let being_processed = Arc::clone(&self.being_processed); 295 | let transition_input_sender = self.transition_input.sender.clone(); 296 | let solver_not_needed_sender = self.solver_not_needed.sender.clone(); 297 | let solver_needed_input_sender = self.solver_needed_input.sender.clone(); 298 | 299 | let main_thread = thread::spawn(move || { 300 | let mut counter = 0; 301 | 302 | loop { 303 | if let Some(next_state) = unprocessed_states.pop() { 304 | counter += 1; 305 | if counter >= 20_000 { 306 | break; 307 | } 308 | // send expansive transtions to worker threads, this blocks when all worker threads 309 | // are used 310 | if symbolic_executor::expensive_computation(&next_state) { 311 | debug!("Sending expensive computation to thread"); 312 | being_processed.fetch_add(1, Ordering::SeqCst); 313 | transition_input_sender.send(next_state); 314 | continue; 315 | } 316 | 317 | let next_id = next_state.id; 318 | let transitions = symbolic_step(&next_state); 319 | 320 | let (conditional, simple): (Vec<_>, Vec<_>) = 321 | transitions.into_iter().partition(|(_, edge_info)| { 322 | if let EdgeType::Cond(_) = edge_info { 323 | true 324 | } else { 325 | false 326 | } 327 | }); 328 | 329 | // handle easy cases first 330 | if !simple.is_empty() { 331 | solver_not_needed_sender.send((next_id, simple)); 332 | being_processed.fetch_add(1, Ordering::SeqCst); 333 | } 334 | 335 | if !conditional.is_empty() { 336 | // send more complex state transitions to be processed async 337 | solver_needed_input_sender.send(SolverInput { 338 | id: next_id, 339 | transitions: conditional, 340 | }); 341 | being_processed.fetch_add(1, Ordering::SeqCst); 342 | } 343 | } else if being_processed.load(Ordering::SeqCst) == 0 344 | && unprocessed_states.is_empty() 345 | { 346 | // if we can not pop a new state and there are no ongoing computations we have 347 | // reached the end of the symbolic execution 348 | break; 349 | } 350 | } 351 | 352 | // shutdown all threads 353 | drop(kill_switch_sender); 354 | }); 355 | 356 | loop { 357 | select! { 358 | recv(self.transition_output.receiver) -> msg => { 359 | let Transition{id: next_id, transitions} = msg.unwrap(); 360 | handle_transitions!(self, next_id, transitions); 361 | } 362 | recv(self.solver_needed_output.receiver) -> msg => { 363 | let SolverOutput{id: next_id, transitions: conditional } = msg.unwrap(); 364 | 365 | for ((state, edge_info), check) in conditional.into_iter() { 366 | let id = state.id; 367 | let state = Arc::new(state); 368 | if check { 369 | self.unprocessed_states.push(Arc::clone(&state)); 370 | self.edges.push(SymbolicEdge::new(next_id, id, edge_info)); 371 | } else { 372 | self 373 | .edges 374 | .push(SymbolicEdge::new(next_id, id, EdgeType::Unsat)); 375 | } 376 | self.states.push(state); 377 | } 378 | } 379 | recv(self.solver_not_needed.receiver) -> msg => { 380 | let (next_id, simple) = msg.unwrap(); 381 | for (state, edge_info) in simple.into_iter() { 382 | let id = state.id; 383 | let state = Arc::new(state); 384 | 385 | if let EdgeType::Terminal = edge_info { 386 | self.end_states.push(Arc::clone(&state)); 387 | self.edges.push(SymbolicEdge::new(next_id, id, edge_info)); 388 | } else { 389 | self.unprocessed_states.push(Arc::clone(&state)); 390 | self.edges.push(SymbolicEdge::new(next_id, id, edge_info)); 391 | } 392 | self.states.push(state); 393 | } 394 | } 395 | recv(self.kill_switch_receiver) -> msg => { 396 | debug_assert!(msg.is_err()); 397 | break; 398 | } 399 | } 400 | 401 | self.being_processed.fetch_sub(1, Ordering::SeqCst); 402 | } 403 | 404 | main_thread.join().expect("Could not close main thread"); 405 | debug_assert_eq!(self.being_processed.load(Ordering::SeqCst), 0); 406 | } 407 | 408 | /// This function only returns states where changes occured 409 | pub fn end_states_storage(&self) -> Vec { 410 | let mut res = vec![]; 411 | for state in &self.end_states { 412 | if state.env != self.initial_state.env { 413 | res.push((**state).clone()); 414 | } 415 | } 416 | res 417 | } 418 | 419 | pub fn initial_state(&self) -> &SeState { 420 | &(*self.initial_state) 421 | } 422 | 423 | // only available for testing, very expensive 424 | #[cfg(test)] 425 | pub fn get_state_by_id(&self, id: usize) -> SeState { 426 | for state in &self.states { 427 | if state.id == id { 428 | return (**state).clone(); 429 | } 430 | } 431 | unreachable!() 432 | } 433 | 434 | pub fn end_states(&self) -> Vec { 435 | let mut res = vec![]; 436 | for state in &self.end_states { 437 | res.push((**state).clone()); 438 | } 439 | res 440 | } 441 | 442 | pub fn to_dot(&self, w: &mut W) -> Result<(), io::Error> { 443 | writeln!(w, "digraph se_graph {{")?; 444 | for s in &self.states { 445 | s.to_dot(w)?; 446 | } 447 | for e in &self.edges { 448 | e.to_dot(w)?; 449 | } 450 | writeln!(w, "}}")?; 451 | Ok(()) 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /esvm/src/test_helpers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::bytecode::Instr; 4 | use crate::disasm::Disasm; 5 | use crate::se::env::Env; 6 | use crate::se::expr::solver::Solvers; 7 | use crate::se::expr::symbolic_memory; 8 | use crate::se::symbolic_analysis::{Context, CONFIG}; 9 | use crate::se::symbolic_graph::SymbolicGraph; 10 | use crate::se::symbolic_state::SeState; 11 | 12 | pub fn generate_test_graph(ins: Vec) -> SymbolicGraph { 13 | let mut env = Env::new(); 14 | let mut memory = symbolic_memory::new_memory(); 15 | 16 | let attacker = env.new_attacker_account(&mut memory); 17 | let victim = env.new_victim_account(&mut memory, &vec![]); 18 | let _hijack = env.new_hijack_account(&mut memory); 19 | let inital_tx = env.new_attacker_tx(&mut memory, attacker, victim); 20 | 21 | let dasm = Disasm::new(ins); 22 | 23 | let config = CONFIG.read().unwrap().clone(); 24 | 25 | let initial_storage = env.get_account(&victim).storage; 26 | let context = Context::new( 27 | config, 28 | dasm, 29 | initial_storage, 30 | Solvers::Yice { 31 | count: num_cpus::get(), 32 | timeout: 120_000, 33 | }, 34 | ); 35 | let state = SeState::new( 36 | Arc::new(context), 37 | Arc::new(memory), 38 | &Arc::new(env), 39 | victim, 40 | inital_tx, 41 | ); 42 | let mut g = SymbolicGraph::new(state); 43 | 44 | g.analyze_graph(); 45 | g 46 | } 47 | 48 | pub fn generate_test_state() -> SeState { 49 | let mut env = Env::new(); 50 | let mut memory = symbolic_memory::new_memory(); 51 | 52 | let attacker = env.new_attacker_account(&mut memory); 53 | let victim = env.new_victim_account(&mut memory, &vec![]); 54 | let _hijack = env.new_hijack_account(&mut memory); 55 | let inital_tx = env.new_attacker_tx(&mut memory, attacker, victim); 56 | 57 | let dasm = Disasm::new(vec![]); 58 | 59 | let config = CONFIG.read().unwrap().clone(); 60 | 61 | let initial_storage = env.get_account(&victim).storage; 62 | let context = Context::new( 63 | config, 64 | dasm, 65 | initial_storage, 66 | Solvers::Yice { 67 | count: num_cpus::get(), 68 | timeout: 120_000, 69 | }, 70 | ); 71 | SeState::new( 72 | Arc::new(context), 73 | Arc::new(memory), 74 | &Arc::new(env), 75 | victim, 76 | inital_tx, 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /esvm/tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | extern crate contracts; 2 | 3 | #[cfg(test)] 4 | mod integration_tests { 5 | use contracts::*; 6 | build_integration_tests!(); 7 | } 8 | -------------------------------------------------------------------------------- /evmexec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evmexec" 3 | version = "0.1.0" 4 | authors = [""] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde_json = "1.0" 9 | serde_derive = "1.0" 10 | serde = "1.0" 11 | tempfile = "3.0" 12 | regex = "1" 13 | hexdecode = "0.2" 14 | subprocess = "0.1" 15 | log = "0.4" 16 | lazy_static = "1.1" 17 | 18 | [dependencies.ethereum-newtypes] 19 | version = "0.1" 20 | path = "ethereum-newtypes" 21 | 22 | [dependencies.uint] 23 | version = "0.4" 24 | features = ["std"] 25 | 26 | [dev-dependencies] 27 | maplit = "1.0" 28 | 29 | [features] 30 | # verbose error output during some operations, e.g., state parsing, this requires some allocations, thus it is disabled by default 31 | verbose = [] 32 | -------------------------------------------------------------------------------- /evmexec/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /evmexec/README.md: -------------------------------------------------------------------------------- 1 | # Evmexec 2 | 3 | A simple rust crate for executing a chain of transactions on a local evm instance. Based on [evmlab](https://github.com/ethereum/evmlab). 4 | 5 | # Dependencies 6 | 7 | This crate expects a valid geth evm instance in your path. Tested on 1.8.17-stable. 8 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethereum-newtypes" 3 | version = "0.1.0" 4 | authors = [""] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | serde = "1.0" 9 | serde_json = "1.0" 10 | serde_derive = "1.0" 11 | hexdecode = "0.2" 12 | 13 | 14 | [dependencies.uint] 15 | version = "0.4" 16 | features = ["std"] 17 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/README.md: -------------------------------------------------------------------------------- 1 | # ethereum-newtypes 2 | 3 | A rust crate which provides newtypes for common ethereum data structures. Mostly Serialize/Deserialize, [Parity API](https://wiki.parity.io/JSONRPC) compliant. 4 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hexdecode; 2 | extern crate serde; 3 | extern crate uint; 4 | 5 | use std::{fmt, io}; 6 | 7 | use serde::{ 8 | de::{self, Visitor}, 9 | Deserialize, Deserializer, Serialize, Serializer, 10 | }; 11 | use uint::U256; 12 | 13 | #[macro_use] 14 | mod macros; 15 | 16 | /// A wrapper around U256, pads to even length by default 17 | impl_u256_newtype!(pub struct WU256(pub U256)); 18 | 19 | /// An ethereum account address, pads to 20 byte hex representation by default 20 | impl_u256_newtype!(pub struct Address(pub U256)); 21 | 22 | /// An ethereum Hash, pads to 32 byte hex representation by default 23 | impl_u256_newtype!(pub struct Hash(pub U256)); 24 | 25 | /// A wrapper around a byte array 26 | #[derive(Clone, Hash, PartialEq, Eq)] 27 | pub struct Bytes(pub Vec); 28 | 29 | // ==================================== 30 | // U256 wrapper 31 | // ==================================== 32 | impl WU256 { 33 | pub fn new(addr: U256) -> Self { 34 | WU256(addr) 35 | } 36 | } 37 | 38 | /// A utility function for deserializing a wrapper type from a usize str representation 39 | pub fn wu256_from_usize_str<'de, D>(deserializer: D) -> Result 40 | where 41 | D: Deserializer<'de>, 42 | { 43 | let s = String::deserialize(deserializer)?; 44 | let u: U256 = U256::from_dec_str(&s).map_err(|e| de::Error::custom(format!("{:?}", e)))?; 45 | Ok(WU256::from(u)) 46 | } 47 | 48 | // oad to even length 49 | impl fmt::LowerHex for WU256 { 50 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 51 | let mut padding = 2; 52 | while padding <= self.0.bits() / 4 && padding < 64 { 53 | padding += 2; 54 | } 55 | write!( 56 | formatter, 57 | "0x{:0>padding$}", 58 | format!("{:x}", self.0), 59 | padding = padding 60 | ) 61 | } 62 | } 63 | 64 | impl<'a> From<&'a str> for WU256 { 65 | fn from(value: &'a str) -> Self { 66 | match hexdecode::decode(value.as_bytes()) { 67 | Ok(bytes) => { 68 | let mut val = U256::from(0); 69 | for (i, byte) in bytes.iter().enumerate() { 70 | if i > 31 { 71 | panic!("Trying to cast bigger then 32 byte value."); 72 | } 73 | val = (val * U256::from(256)) | (U256::from(255) & U256::from(*byte)); 74 | } 75 | val.into() 76 | } 77 | Err(e) => panic!("{:?}", e), 78 | } 79 | } 80 | } 81 | // ==================================== 82 | // U256 wrapper end 83 | // ==================================== 84 | 85 | // ==================================== 86 | // Address 87 | // ==================================== 88 | impl Address { 89 | pub fn new(addr: U256) -> Self { 90 | assert!(addr.bits() <= 160, "{:x} needs {} bit", addr, addr.bits()); 91 | Address(addr) 92 | } 93 | } 94 | 95 | // we pad to 40 byte addresses by default 96 | impl fmt::LowerHex for Address { 97 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 98 | write!(formatter, "0x{:0>40}", format!("{:x}", self.0)) 99 | } 100 | } 101 | 102 | impl From for Address { 103 | fn from(value: WU256) -> Self { 104 | Address::new(value.0) 105 | } 106 | } 107 | 108 | impl<'a> From<&'a str> for Address { 109 | fn from(value: &'a str) -> Self { 110 | match hexdecode::decode(value.as_bytes()) { 111 | Ok(bytes) => { 112 | let mut val = U256::from(0); 113 | for (i, byte) in bytes.iter().enumerate() { 114 | if i > 19 { 115 | panic!("Trying to cast bigger then 20 byte value."); 116 | } 117 | val = (val * U256::from(256)) | (U256::from(255) & U256::from(*byte)); 118 | } 119 | val.into() 120 | } 121 | Err(e) => panic!("{:?}", e), 122 | } 123 | } 124 | } 125 | // ==================================== 126 | // Address end 127 | // ==================================== 128 | 129 | // ==================================== 130 | // Hash 131 | // ==================================== 132 | impl fmt::LowerHex for Hash { 133 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 134 | write!(formatter, "0x{:0>64}", format!("{:x}", self.0)) 135 | } 136 | } 137 | 138 | impl Hash { 139 | pub fn new(hash: U256) -> Self { 140 | Hash(hash) 141 | } 142 | } 143 | // ==================================== 144 | // Hash end 145 | // ==================================== 146 | 147 | // ==================================== 148 | // Bytes 149 | // ==================================== 150 | impl Bytes { 151 | pub fn from_hex_str(input: &str) -> Result { 152 | hexdecode::decode(input.as_bytes()).map(|v: Vec| v.into()) 153 | } 154 | 155 | pub fn to_hex(&self) -> String { 156 | let mut s = String::with_capacity((self.0.len() * 2) + 3); 157 | s.push_str("0x"); 158 | for byte in &self.0 { 159 | s.push_str(&format!("{:02x}", byte)); 160 | } 161 | s 162 | } 163 | 164 | // some common convenience methods 165 | pub fn as_slice(&self) -> &[u8] { 166 | self.0.as_slice() 167 | } 168 | 169 | pub fn len(&self) -> usize { 170 | self.0.len() 171 | } 172 | 173 | pub fn is_empty(&self) -> bool { 174 | self.0.is_empty() 175 | } 176 | } 177 | 178 | impl fmt::Debug for Bytes { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | self.0.fmt(f) 181 | } 182 | } 183 | 184 | impl AsRef<[u8]> for Bytes { 185 | fn as_ref(&self) -> &[u8] { 186 | &self.0 187 | } 188 | } 189 | 190 | impl From> for Bytes { 191 | fn from(value: Vec) -> Self { 192 | Bytes(value) 193 | } 194 | } 195 | 196 | impl fmt::Display for Bytes { 197 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 198 | write!(f, "{}", self.to_hex()) 199 | } 200 | } 201 | 202 | // serialze 203 | impl Serialize for Bytes { 204 | fn serialize(&self, serializer: S) -> Result 205 | where 206 | S: Serializer, 207 | { 208 | serializer.serialize_str(&format!("{}", self)) 209 | } 210 | } 211 | 212 | impl<'de> Deserialize<'de> for Bytes { 213 | fn deserialize(deserializer: D) -> Result 214 | where 215 | D: Deserializer<'de>, 216 | { 217 | struct BytesVisitor; 218 | 219 | impl<'de> Visitor<'de> for BytesVisitor { 220 | type Value = Bytes; 221 | 222 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 223 | formatter.write_str("Hex encoded byte array.") 224 | } 225 | 226 | fn visit_str(self, value: &str) -> Result 227 | where 228 | E: de::Error, 229 | { 230 | hexdecode::decode(value.as_bytes()) 231 | .map(|v: Vec<_>| v.into()) 232 | .map_err(|e| de::Error::custom(format!("{:?}", e))) 233 | } 234 | } 235 | 236 | deserializer.deserialize_str(BytesVisitor) 237 | } 238 | } 239 | // ==================================== 240 | // Bytes end 241 | // ==================================== 242 | 243 | #[cfg(test)] 244 | mod tests { 245 | use super::*; 246 | 247 | #[test] 248 | fn display_u256_test() { 249 | assert_eq!(&format!("{:x}", WU256::from(0x1)), "0x01"); 250 | assert_eq!(&format!("{:x}", WU256::from(0x1111)), "0x1111"); 251 | assert_eq!(&format!("{:x}", WU256::from(0x11111)), "0x011111"); 252 | } 253 | 254 | #[test] 255 | fn deserialize_test() { 256 | let value = WU256::from( 257 | U256::from_dec_str("685244925327644839234826974078916142077323599782").unwrap(), 258 | ); 259 | let json_string = serde_json::to_string(&value).unwrap(); 260 | let deserialized: WU256 = serde_json::from_str(&json_string).unwrap(); 261 | assert_eq!( 262 | WU256::from( 263 | U256::from_dec_str("685244925327644839234826974078916142077323599782").unwrap() 264 | ), 265 | deserialized, 266 | ); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /evmexec/ethereum-newtypes/src/macros.rs: -------------------------------------------------------------------------------- 1 | // some helper macros for deriving traits, why is this a thing? 2 | macro_rules! impl_from_trait { 3 | ($name:ident from $type:ty) => { 4 | impl From<$type> for $name { 5 | fn from(value: $type) -> Self { 6 | Self::new(value.into()) 7 | } 8 | } 9 | }; 10 | } 11 | 12 | macro_rules! impl_visitor_fumc { 13 | ($func_name:ident for $name:ident from $type:ty) => { 14 | fn $func_name(self, value: $type) -> Result 15 | where 16 | E: de::Error, 17 | { 18 | Ok($name::from(value)) 19 | } 20 | } 21 | } 22 | 23 | macro_rules! impl_u256_newtype { 24 | ( 25 | $(#[$struct_attr:meta])* 26 | pub struct $name:ident(pub U256) 27 | ) => { 28 | 29 | // definition 30 | $(#[$struct_attr])* 31 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 32 | pub struct $name(pub U256); 33 | 34 | 35 | // from traits 36 | impl From for $name { 37 | fn from(value: U256) -> Self { 38 | Self::new(value) 39 | } 40 | } 41 | 42 | impl<'a> From<&'a U256> for $name { 43 | fn from(value: &'a U256) -> Self { 44 | Self::new(value.into()) 45 | } 46 | } 47 | 48 | impl From<[u8; 32]> for $name { 49 | fn from(value: [u8; 32]) -> Self { 50 | Self::new(value.into()) 51 | } 52 | } 53 | 54 | impl<'a> From<&'a [u8; 32]> for $name { 55 | fn from(value: &'a [u8; 32]) -> Self { 56 | Self::new(value.into()) 57 | } 58 | } 59 | 60 | impl_from_trait!($name from usize); 61 | impl_from_trait!($name from u64); 62 | impl_from_trait!($name from u32); 63 | impl_from_trait!($name from u16); 64 | impl_from_trait!($name from u8); 65 | 66 | impl_from_trait!($name from isize); 67 | impl_from_trait!($name from i64); 68 | impl_from_trait!($name from i32); 69 | impl_from_trait!($name from i16); 70 | impl_from_trait!($name from i8); 71 | 72 | impl ::std::ops::AddAssign for $name { 73 | fn add_assign(&mut self, other: $name) { 74 | self.0.add_assign(other.0) 75 | } 76 | } 77 | 78 | impl ::std::ops::SubAssign for $name { 79 | fn sub_assign(&mut self, other: $name) { 80 | self.0.sub_assign(other.0) 81 | } 82 | } 83 | 84 | // serialze 85 | impl Serialize for $name { 86 | fn serialize(&self, serializer: S) -> Result 87 | where 88 | S: Serializer, 89 | { 90 | serializer.serialize_str(&format!("{:x}", self)) // this assures we use padded value only 91 | } 92 | } 93 | 94 | 95 | // deserialize 96 | impl<'de> Deserialize<'de> for $name { 97 | fn deserialize(deserializer: D) -> Result<$name, D::Error> 98 | where 99 | D: Deserializer<'de>, 100 | { 101 | struct ValVisitor; 102 | 103 | impl<'de> Visitor<'de> for ValVisitor { 104 | type Value = $name; 105 | 106 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 107 | formatter.write_str("expected hex encoded U256 value.") 108 | } 109 | 110 | fn visit_str(self, value: &str) -> Result 111 | where 112 | E: de::Error, 113 | { 114 | let bytes: Vec = hexdecode::decode(&value).map_err(|e| de::Error::custom(format!("{:?}", e)))?; 115 | 116 | let mut val = U256::from(0); 117 | for (i, byte) in bytes.iter().enumerate() { 118 | if i > 31 { 119 | return Err(de::Error::custom(format!("Trying to deserialize bigger then 32 byte value: {:?}", bytes))); 120 | } 121 | val = (val * U256::from(256)) | (U256::from(255) & U256::from(*byte)); 122 | } 123 | Ok(val.into()) 124 | } 125 | 126 | 127 | fn visit_string(self, value: String) -> Result 128 | where 129 | E: de::Error, 130 | { 131 | self.visit_str(value.as_ref()) 132 | } 133 | 134 | impl_visitor_fumc!(visit_u64 for $name from u64); 135 | impl_visitor_fumc!(visit_u32 for $name from u32); 136 | impl_visitor_fumc!(visit_u16 for $name from u16); 137 | impl_visitor_fumc!(visit_u8 for $name from u8); 138 | 139 | impl_visitor_fumc!(visit_i64 for $name from i64); 140 | impl_visitor_fumc!(visit_i32 for $name from i32); 141 | impl_visitor_fumc!(visit_i16 for $name from i16); 142 | impl_visitor_fumc!(visit_i8 for $name from i8); 143 | } 144 | 145 | deserializer.deserialize_any(ValVisitor) 146 | } 147 | } 148 | 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /evmexec/src/evm.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | error::Error, 4 | io::{self, prelude::*, BufReader, Read}, 5 | }; 6 | 7 | use ethereum_newtypes::{Address, Bytes, Hash, WU256}; 8 | use log::debug; 9 | use subprocess::{Exec, Redirection}; 10 | use tempfile::TempPath; 11 | 12 | use crate::{ 13 | evmtrace::{ContextParser, Instruction, InstructionContext}, 14 | genesis::{Account, Genesis}, 15 | }; 16 | 17 | /// A struct to hold the evm information 18 | pub struct Evm { 19 | /// The Genesis file for the current execution 20 | pub genesis: Genesis, 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct EvmInput { 25 | pub input_data: Bytes, 26 | pub sender: Address, 27 | pub receiver: Address, 28 | pub gas: u32, 29 | pub value: WU256, 30 | } 31 | 32 | impl Default for Evm { 33 | fn default() -> Self { 34 | Self::new() 35 | } 36 | } 37 | 38 | impl From for Evm { 39 | fn from(genesis: Genesis) -> Self { 40 | Self { genesis } 41 | } 42 | } 43 | 44 | impl Evm { 45 | pub fn new() -> Self { 46 | let genesis = Genesis::new(); 47 | Self { genesis } 48 | } 49 | 50 | pub fn execute(self, input: EvmInput) -> Execution { 51 | let res = self.execute_input(&input); 52 | Execution { 53 | genesis: self.genesis, 54 | input, 55 | result: res, 56 | } 57 | } 58 | 59 | fn execute_input(&self, input: &EvmInput) -> Result { 60 | let (output, _path) = self.execute_vm(input)?; 61 | self.parse_trace(output, input.receiver.clone()) 62 | } 63 | 64 | fn execute_vm( 65 | &self, 66 | input: &EvmInput, 67 | ) -> Result<(BufReader>, TempPath), crate::Error> { 68 | let path = self.genesis.export()?.into_temp_path(); 69 | let args = [ 70 | "--prestate", 71 | path.to_str() 72 | .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?, 73 | "--gas", 74 | &format!("{}", input.gas), 75 | "--sender", 76 | &format!("{:x}", input.sender), 77 | "--receiver", 78 | &format!("{:x}", input.receiver), 79 | "--value", 80 | &format!("{:x}", input.value), 81 | "--input", 82 | &encode(&input.input_data.as_slice(), &Prefixed::No), 83 | "--json", 84 | "--dump", 85 | "run", 86 | ]; 87 | 88 | if cfg!(feature = "verbose") { 89 | let mut debug = String::new(); 90 | for arg in &args { 91 | debug.push_str(&format!(" {}", arg)); 92 | } 93 | debug!("Starting evm with arguments: {:x?}", debug); 94 | } 95 | 96 | let read_handle = match Exec::cmd("evm") 97 | .args(&args) 98 | .stderr(Redirection::Merge) // redirect err output to stdout 99 | .stream_stdout() 100 | { 101 | Err(why) => panic!("couldn't spawn evm: {}", why.description()), 102 | Ok(process) => process, 103 | }; 104 | // also return the path object to ensure the temporary file does not get dropped until the 105 | // output of the exeution is read 106 | Ok((BufReader::new(Box::new(read_handle)), path)) 107 | } 108 | 109 | fn parse_trace( 110 | &self, 111 | mut reader: BufReader>, 112 | receiver: Address, 113 | ) -> Result { 114 | let mut buf = String::new(); 115 | let mut instructions = Vec::new(); 116 | let mut parser = ContextParser::new(receiver); 117 | 118 | while let Ok(d) = reader.read_line(&mut buf) { 119 | // end of stream 120 | if d == 0 { 121 | break; 122 | } 123 | if buf.contains("Fatal") { 124 | return Err(crate::Error::custom(format!( 125 | "Could not fetch evm output: {}", 126 | buf, 127 | ))); 128 | } 129 | 130 | // detect end of the trace 131 | if buf.contains("output") { 132 | break; 133 | } else if let Some(ins) = parser.parse_trace_line(&buf) { 134 | instructions.push(ins); 135 | } 136 | 137 | // clear buffer for reuse 138 | buf.clear(); 139 | } 140 | 141 | let new_state = if cfg!(feature = "verbose") { 142 | buf.clear(); 143 | while let Ok(d) = reader.read_line(&mut buf) { 144 | if d == 0 { 145 | break; 146 | } 147 | } 148 | let state = serde_json::from_str(&buf); 149 | if state.is_err() { 150 | eprintln!("Error during parsing new state:\n{}\n{:?}", buf, state); 151 | } else { 152 | debug!("New state after tx execuction:\n{:?}", state) 153 | } 154 | state 155 | } else { 156 | serde_json::from_reader(reader) 157 | }; 158 | 159 | Ok(ExecutionResult { 160 | trace: instructions, 161 | new_state: new_state?, 162 | }) 163 | } 164 | } 165 | 166 | #[derive(Debug)] 167 | pub struct Execution { 168 | pub genesis: Genesis, 169 | pub input: EvmInput, 170 | pub result: Result, 171 | } 172 | 173 | impl Execution { 174 | /// Transforms the Execution into a new evm to be executed 175 | pub fn into_evm(self) -> Evm { 176 | Evm { 177 | genesis: self.genesis, 178 | } 179 | } 180 | 181 | /// Updates the environment based on the execution result, returns an error if the execution did not 182 | /// succeed 183 | pub fn into_evm_updated(mut self) -> Result { 184 | let res = self.result?; 185 | 186 | for InstructionContext { 187 | executed_on, 188 | instruction, 189 | } in res.trace 190 | { 191 | if let Instruction::SStore { addr, value } = instruction { 192 | self.genesis 193 | .update_account_storage(&executed_on, addr, value)?; 194 | } 195 | } 196 | 197 | // jsut overwrite, we have to iterate each account anyway 198 | for (addr, acc_state) in res.new_state.accounts { 199 | if let Some(acc) = self.genesis.alloc.get_mut(&addr) { 200 | acc.balance = acc_state.balance; 201 | } else { 202 | // new account created during execution 203 | self.genesis.add_account(addr, acc_state); 204 | } 205 | } 206 | 207 | self.genesis 208 | .alloc 209 | .get_mut(&self.input.sender) 210 | .ok_or_else(|| { 211 | crate::Error::custom("Could not find sender for nonce update".to_string()) 212 | })? 213 | .nonce += 1.into(); 214 | 215 | Ok(Evm { 216 | genesis: self.genesis, 217 | }) 218 | } 219 | 220 | pub fn is_err(&self) -> bool { 221 | self.result.is_err() 222 | } 223 | } 224 | 225 | #[derive(Debug, Deserialize)] 226 | pub struct State { 227 | root: Hash, 228 | accounts: HashMap, 229 | } 230 | 231 | #[derive(Debug)] 232 | pub struct ExecutionResult { 233 | pub trace: Vec, 234 | pub new_state: State, 235 | } 236 | 237 | // I just want a named boolean 238 | enum Prefixed { 239 | Yes, 240 | No, 241 | } 242 | 243 | fn encode(input: &[u8], prefixed: &Prefixed) -> String { 244 | let mut s = String::with_capacity((input.len() * 2) + 2); 245 | if let Prefixed::Yes = prefixed { 246 | s.push_str("0x"); 247 | } 248 | for byte in input { 249 | s.push_str(&format!("{:0>2x}", byte)); 250 | } 251 | s 252 | } 253 | 254 | #[cfg(test)] 255 | mod tests { 256 | use super::*; 257 | 258 | use crate::{evmtrace::Instruction, genesis::Account}; 259 | use ethereum_newtypes::WU256; 260 | use std::rc::Rc; 261 | 262 | #[test] 263 | fn vm_execution() { 264 | let result = execute_test_case() 265 | .result 266 | .expect("Detected error while executing evm"); 267 | let writes = vec![InstructionContext { 268 | executed_on: Rc::new("0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()), 269 | instruction: Instruction::SStore { 270 | addr: 0.into(), 271 | value: "0xDFA72DE72F96CF5B127B070E90D68EC9710797C".into(), 272 | }, 273 | }]; 274 | assert_eq!(writes, result.trace); 275 | } 276 | 277 | #[test] 278 | fn vm_update_state() { 279 | let update = execute_test_case() 280 | .into_evm_updated() 281 | .expect("Error while updating file"); 282 | assert_eq!( 283 | update.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].storage 284 | [&0.into()], 285 | WU256::from("0xDFA72DE72F96CF5B127B070E90D68EC9710797C"), 286 | ); 287 | } 288 | 289 | // see https://github.com/ethereum/go-ethereum/issues/17969 290 | #[test] 291 | fn deseriaize_malformed_account() { 292 | let bytes = include_bytes!("../tests/files/corrupted_geth.json"); 293 | let state: State = serde_json::from_slice(bytes).unwrap(); 294 | assert_eq!( 295 | state.accounts[&WU256::from("0xccfaee7dd7e330960d5241a980415cc94dbe59a4").into()] 296 | .storage, 297 | HashMap::new() 298 | ); 299 | } 300 | 301 | fn execute_test_case() -> Execution { 302 | let mut evm = Evm::new(); 303 | 304 | evm.genesis.add_account( 305 | "0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into(), 306 | Account::new(0x10.into(), None, 0x2.into(), None), 307 | ); 308 | 309 | let code = hexdecode::decode("60806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637c52e3258114610050578063e9ca826c14610080575b600080fd5b34801561005c57600080fd5b5061007e73ffffffffffffffffffffffffffffffffffffffff60043516610095565b005b34801561008c57600080fd5b5061007e610145565b60005473ffffffffffffffffffffffffffffffffffffffff1633146100b657fe5b600154604080517f338ccd7800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529151919092169163338ccd7891602480830192600092919082900301818387803b15801561012a57600080fd5b505af115801561013e573d6000803e3d6000fd5b5050505050565b6000805473ffffffffffffffffffffffffffffffffffffffff1916331790555600a165627a7a72305820b376cbf41ad45cba2c20890893f93f24efe850bf7eaf35fd12a0474576b4ac2d0029".as_bytes()).expect("Could not parse code array"); 310 | evm.genesis.add_account( 311 | "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 312 | Account::new( 313 | 0x0.into(), 314 | Some(code.into()), 315 | 0x1.into(), 316 | Some(hashmap! { 317 | 0x0.into() => "0xdfa72de72f96cf5b127b070e90d68ec9710797c".into(), 318 | 0x1.into() => "0x6c249452ee469d839942e05b8492dbb9f9c70ac".into(), 319 | }), 320 | ), 321 | ); 322 | 323 | let data = Bytes::from_hex_str("0xe9ca826c000000").expect("Could not parse input"); 324 | let input = EvmInput { 325 | input_data: data, 326 | sender: "0xdfa72de72f96cf5b127b070e90d68ec9710797c".into(), 327 | receiver: "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 328 | gas: 10_000, 329 | value: 0.into(), 330 | }; 331 | evm.execute(input) 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /evmexec/src/genesis.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::{Seek, SeekFrom, Write}, 4 | }; 5 | 6 | use ethereum_newtypes::{wu256_from_usize_str, Address, Bytes, Hash, WU256}; 7 | use log::debug; 8 | use serde::{Deserialize, Deserializer}; 9 | use serde_json::Value; 10 | use tempfile::{Builder, NamedTempFile}; 11 | 12 | use crate::Error; 13 | 14 | #[derive(Debug, Serialize, Deserialize)] 15 | #[serde(rename_all = "camelCase")] 16 | pub struct Config { 17 | pub eip150_block: u32, 18 | pub eip155_block: u32, 19 | pub eip158_block: u32, 20 | pub homestead_block: u32, 21 | pub dao_fork_block: u32, 22 | pub byzantium_block: u32, 23 | pub constantinople_block: u32, 24 | } 25 | 26 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 27 | pub struct Account { 28 | #[serde(deserialize_with = "wu256_from_usize_str")] 29 | pub balance: WU256, 30 | #[serde(deserialize_with = "code_or_default", default = "default_bytes")] 31 | pub code: Bytes, 32 | pub nonce: WU256, 33 | #[serde(deserialize_with = "ok_or_default", default = "default_storage")] 34 | pub storage: HashMap, 35 | } 36 | 37 | fn default_bytes() -> Bytes { 38 | vec![].into() 39 | } 40 | 41 | fn code_or_default<'de, D>(deserializer: D) -> Result 42 | where 43 | D: Deserializer<'de>, 44 | { 45 | let v: Value = Deserialize::deserialize(deserializer)?; 46 | Ok(Bytes::deserialize(v).unwrap_or(default_bytes())) 47 | } 48 | 49 | fn default_storage() -> HashMap { 50 | HashMap::new() 51 | } 52 | 53 | fn ok_or_default<'de, D>(deserializer: D) -> Result, D::Error> 54 | where 55 | D: Deserializer<'de>, 56 | { 57 | let v: Value = Deserialize::deserialize(deserializer)?; 58 | Ok(HashMap::deserialize(v).unwrap_or(default_storage())) 59 | } 60 | 61 | impl Account { 62 | pub fn new( 63 | balance: WU256, 64 | code: Option, 65 | nonce: WU256, 66 | storage: Option>, 67 | ) -> Self { 68 | let code = if let Some(c) = code { c } else { vec![].into() }; 69 | let storage = if let Some(s) = storage { 70 | s 71 | } else { 72 | HashMap::new() 73 | }; 74 | Self { 75 | balance, 76 | code, 77 | nonce, 78 | storage, 79 | } 80 | } 81 | } 82 | 83 | impl Config { 84 | pub fn byzantium() -> Self { 85 | let eip150_block = 0; 86 | let eip158_block = 0; 87 | let eip155_block = 0; 88 | let homestead_block = 0; 89 | let dao_fork_block = 0; 90 | let byzantium_block = 2000; 91 | let constantinople_block = 2000; 92 | 93 | Self { 94 | eip150_block, 95 | eip155_block, 96 | eip158_block, 97 | homestead_block, 98 | dao_fork_block, 99 | byzantium_block, 100 | constantinople_block, 101 | } 102 | } 103 | } 104 | 105 | #[derive(Debug, Serialize)] 106 | #[serde(rename_all = "camelCase")] 107 | pub struct Genesis { 108 | pub difficulty: WU256, 109 | pub coinbase: Address, 110 | pub timestamp: WU256, 111 | pub number: WU256, 112 | pub gas_limit: WU256, 113 | pub extra_data: WU256, 114 | pub mixhash: Hash, 115 | pub parent_hash: Hash, 116 | pub nonce: WU256, 117 | pub alloc: HashMap, 118 | pub config: Config, 119 | } 120 | 121 | impl Default for Genesis { 122 | fn default() -> Self { 123 | Self::new() 124 | } 125 | } 126 | 127 | impl Genesis { 128 | pub fn new() -> Self { 129 | let coinbase = Address::new(0.into()); 130 | let gas_limit = 0x003D_0900.into(); 131 | let timestamp = 0x0.into(); 132 | let difficulty = 0x1.into(); 133 | let number = 0x0.into(); 134 | let config = Config::byzantium(); 135 | let extra_data = 0.into(); 136 | let mixhash = 0.into(); 137 | let parent_hash = 0.into(); 138 | let nonce = 0.into(); 139 | let alloc = HashMap::new(); 140 | 141 | Self { 142 | coinbase, 143 | gas_limit, 144 | timestamp, 145 | difficulty, 146 | number, 147 | extra_data, 148 | mixhash, 149 | parent_hash, 150 | nonce, 151 | alloc, 152 | config, 153 | } 154 | } 155 | 156 | /// create a temporary file for holding the configuration 157 | pub fn export(&self) -> Result { 158 | let mut named_tempfile = Builder::new() 159 | .prefix("genesis-state-") 160 | .suffix(".json") 161 | .rand_bytes(32) 162 | .tempfile()?; 163 | 164 | if cfg!(feature = "verbose") { 165 | debug!( 166 | "Generated new genesis file:\n{}", 167 | serde_json::to_string(&self)? 168 | ); 169 | } 170 | 171 | write!(named_tempfile, "{}", serde_json::to_string(&self)?)?; 172 | named_tempfile.seek(SeekFrom::Start(0))?; 173 | 174 | Ok(named_tempfile) 175 | } 176 | 177 | pub fn add_account(&mut self, addr: Address, acc: Account) -> Option { 178 | self.alloc.insert(addr, acc) 179 | } 180 | 181 | pub fn update_account_storage( 182 | &mut self, 183 | account: &Address, 184 | addr: WU256, 185 | value: WU256, 186 | ) -> Result<(), crate::Error> { 187 | let account = self 188 | .alloc 189 | .get_mut(account) 190 | .ok_or_else(|| crate::Error::custom("Trying to update unknown accout".to_string()))?; 191 | account.storage.insert(addr, value); 192 | Ok(()) 193 | } 194 | } 195 | 196 | #[cfg(test)] 197 | mod tests { 198 | use super::*; 199 | use std::{fs::File, io::Read}; 200 | 201 | #[test] 202 | fn create_simple_genesis_() { 203 | let g = Genesis::new(); 204 | let mut f = g.export().expect("Could not export genesis file"); 205 | let mut s = String::new(); 206 | f.read_to_string(&mut s) 207 | .expect("Could not read into Buffer"); 208 | 209 | let correct = "{\"difficulty\":\"0x01\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"timestamp\":\"0x00\",\"number\":\"0x00\",\"gasLimit\":\"0x3d0900\",\"extraData\":\"0x00\",\"mixhash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x00\",\"alloc\":{},\"config\":{\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"homesteadBlock\":0,\"daoForkBlock\":0,\"byzantiumBlock\":2000,\"constantinopleBlock\":2000}}"; 210 | 211 | assert_eq!(s, correct); 212 | } 213 | 214 | #[test] 215 | fn read_temp_file() { 216 | let g = Genesis::new(); 217 | let fpath = g 218 | .export() 219 | .expect("Could not export genesis file") 220 | .into_temp_path(); 221 | let mut f = File::open(fpath).expect("Could not open tmp file"); 222 | let mut s = String::new(); 223 | f.read_to_string(&mut s) 224 | .expect("Could not read into Buffer"); 225 | 226 | let correct = "{\"difficulty\":\"0x01\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"timestamp\":\"0x00\",\"number\":\"0x00\",\"gasLimit\":\"0x3d0900\",\"extraData\":\"0x00\",\"mixhash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"nonce\":\"0x00\",\"alloc\":{},\"config\":{\"eip150Block\":0,\"eip155Block\":0,\"eip158Block\":0,\"homesteadBlock\":0,\"daoForkBlock\":0,\"byzantiumBlock\":2000,\"constantinopleBlock\":2000}}"; 227 | 228 | assert_eq!(s, correct); 229 | } 230 | 231 | #[test] 232 | fn deseriaize_accounts() { 233 | let states: HashMap = serde_json::from_str(&STATE_EXAMPLE).unwrap(); 234 | let mut correct = HashMap::new(); 235 | 236 | let acc_1 = Account { 237 | balance: 0.into(), 238 | nonce : 1.into(), 239 | code: Bytes::from_hex_str("60806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637c52e3258114610050578063e9ca826c14610080575b600080fd5b34801561005c57600080fd5b5061007e73ffffffffffffffffffffffffffffffffffffffff60043516610095565b005b34801561008c57600080fd5b5061007e610145565b60005473ffffffffffffffffffffffffffffffffffffffff1633146100b657fe5b600154604080517f338ccd7800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529151919092169163338ccd7891602480830192600092919082900301818387803b15801561012a57600080fd5b505af115801561013e573d6000803e3d6000fd5b5050505050565b6000805473ffffffffffffffffffffffffffffffffffffffff1916331790555600a165627a7a72305820b376cbf41ad45cba2c20890893f93f24efe850bf7eaf35fd12a0474576b4ac2d0029").unwrap(), 240 | storage: HashMap::new(), 241 | }; 242 | correct.insert("0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), acc_1); 243 | 244 | let acc_2 = Account { 245 | balance: 16.into(), 246 | nonce: 1.into(), 247 | code: vec![].into(), 248 | storage: HashMap::new(), 249 | }; 250 | correct.insert("0dfa72de72f96cf5b127b070e90d68ec9710797c".into(), acc_2); 251 | 252 | let acc_3 = Account { 253 | balance: 1048576.into(), 254 | nonce: 1.into(), 255 | code: Bytes::from_hex_str("606060405260043610603e5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663338ccd7881146043575b600080fd5b3415604d57600080fd5b606c73ffffffffffffffffffffffffffffffffffffffff60043516606e565b005b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614609257fe5b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151560e857600080fd5b505600a165627a7a72305820d94e263975863b2024dc4bfaba0287941709bc576381ae567f9683d8fc2052940029").unwrap(), 256 | storage: hashmap!( 257 | 0.into() => "940ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 258 | ), 259 | }; 260 | correct.insert("86c249452ee469d839942e05b8492dbb9f9c70ac".into(), acc_3); 261 | 262 | assert_eq!(correct, states); 263 | } 264 | 265 | const STATE_EXAMPLE: &'static str = r#"{ 266 | "0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9": { 267 | "balance": "0", 268 | "nonce": 1, 269 | "root": "0fbf6822b39f67831c32e34b61de28604106292b61d87acc5d74a987f320ff2a", 270 | "codeHash": "870095b70f331f4ba54e5fba6ca45ae54a80e81cd9d279105af5be8a0fd1a2ca", 271 | "code": "60806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637c52e3258114610050578063e9ca826c14610080575b600080fd5b34801561005c57600080fd5b5061007e73ffffffffffffffffffffffffffffffffffffffff60043516610095565b005b34801561008c57600080fd5b5061007e610145565b60005473ffffffffffffffffffffffffffffffffffffffff1633146100b657fe5b600154604080517f338ccd7800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529151919092169163338ccd7891602480830192600092919082900301818387803b15801561012a57600080fd5b505af115801561013e573d6000803e3d6000fd5b5050505050565b6000805473ffffffffffffffffffffffffffffffffffffffff1916331790555600a165627a7a72305820b376cbf41ad45cba2c20890893f93f24efe850bf7eaf35fd12a0474576b4ac2d0029", 272 | "storage": {} 273 | }, 274 | "0dfa72de72f96cf5b127b070e90d68ec9710797c": { 275 | "balance": "16", 276 | "nonce": 1, 277 | "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", 278 | "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", 279 | "code": "", 280 | "storage": {} 281 | }, 282 | "86c249452ee469d839942e05b8492dbb9f9c70ac": { 283 | "balance": "1048576", 284 | "nonce": 1, 285 | "root": "d80f75b929e8c410d0edeba934c1b41b338a362cd75b92c2dcbb8d0a0cf55961", 286 | "codeHash": "097398617ee709f79e1f68999b9371d7bf2dea11f988ac67213b3bd3f607d9d8", 287 | "code": "606060405260043610603e5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663338ccd7881146043575b600080fd5b3415604d57600080fd5b606c73ffffffffffffffffffffffffffffffffffffffff60043516606e565b005b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614609257fe5b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151560e857600080fd5b505600a165627a7a72305820d94e263975863b2024dc4bfaba0287941709bc576381ae567f9683d8fc2052940029", 288 | "storage": { 289 | "0000000000000000000000000000000000000000000000000000000000000000": "940ad62f08b3b9f0ecc7251befbeff80c9bb488fe9" 290 | } 291 | } 292 | }"#; 293 | } 294 | -------------------------------------------------------------------------------- /evmexec/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate ethereum_newtypes; 2 | extern crate hexdecode; 3 | extern crate regex; 4 | extern crate serde; 5 | extern crate serde_json; 6 | extern crate subprocess; 7 | extern crate tempfile; 8 | extern crate uint; 9 | 10 | #[macro_use] 11 | extern crate lazy_static; 12 | #[macro_use] 13 | extern crate serde_derive; 14 | #[macro_use] 15 | #[cfg(test)] 16 | extern crate maplit; 17 | 18 | pub mod evm; 19 | pub mod evmtrace; 20 | pub mod genesis; 21 | 22 | use std::io; 23 | 24 | macro_rules! impl_error { 25 | ( 26 | $(#[$struct_attr:meta])* 27 | pub enum Error { 28 | $( $enum_variant_name:ident($error_type:path), )+ 29 | } 30 | ) => { 31 | // meta attributes 32 | $(#[$struct_attr])* 33 | // enum definition 34 | pub enum Error { 35 | $( $enum_variant_name($error_type), )+ 36 | } 37 | 38 | // impl error conversion for each type 39 | $( 40 | impl From<$error_type> for Error { 41 | fn from(error: $error_type) -> Self { 42 | Error::$enum_variant_name(error) 43 | } 44 | } 45 | )+ 46 | }; 47 | } 48 | 49 | impl_error!( 50 | #[derive(Debug)] 51 | pub enum Error { 52 | Io(io::Error), 53 | SerdeJson(serde_json::Error), 54 | Custom(String), 55 | } 56 | ); 57 | 58 | impl Error { 59 | fn custom(s: String) -> Self { 60 | Error::Custom(s) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /evmexec/tests/multiple_transactions_test.rs: -------------------------------------------------------------------------------- 1 | extern crate ethereum_newtypes; 2 | extern crate evmexec; 3 | 4 | #[macro_use] 5 | extern crate maplit; 6 | 7 | use ethereum_newtypes::Bytes; 8 | use evmexec::{ 9 | evm::{Evm, EvmInput}, 10 | genesis::Account, 11 | }; 12 | 13 | fn setup_evm() -> Evm { 14 | let mut evm = Evm::new(); 15 | 16 | evm.genesis.add_account( 17 | "0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into(), 18 | Account::new(0x0.into(), None, 0x1.into(), None), 19 | ); 20 | 21 | let code = hexdecode::decode("60806040526004361061004b5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637c52e3258114610050578063e9ca826c14610080575b600080fd5b34801561005c57600080fd5b5061007e73ffffffffffffffffffffffffffffffffffffffff60043516610095565b005b34801561008c57600080fd5b5061007e610145565b60005473ffffffffffffffffffffffffffffffffffffffff1633146100b657fe5b600154604080517f338ccd7800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529151919092169163338ccd7891602480830192600092919082900301818387803b15801561012a57600080fd5b505af115801561013e573d6000803e3d6000fd5b5050505050565b6000805473ffffffffffffffffffffffffffffffffffffffff1916331790555600a165627a7a72305820b376cbf41ad45cba2c20890893f93f24efe850bf7eaf35fd12a0474576b4ac2d0029".as_bytes()).expect("Could not parse code array"); 22 | evm.genesis.add_account( 23 | "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 24 | Account::new( 25 | 0x0.into(), 26 | Some(code.into()), 27 | 0x1.into(), 28 | Some(hashmap!{ 29 | 0x0.into() => "0x00".into(), 30 | 0x1.into() => "0x6c249452ee469d839942e05b8492dbb9f9c70ac".into(), 31 | }), 32 | ), 33 | ); 34 | 35 | let code = hexdecode::decode("0x606060405260043610603e5763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663338ccd7881146043575b600080fd5b3415604d57600080fd5b606c73ffffffffffffffffffffffffffffffffffffffff60043516606e565b005b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614609257fe5b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050151560e857600080fd5b505600a165627a7a72305820d94e263975863b2024dc4bfaba0287941709bc576381ae567f9683d8fc2052940029".as_bytes()).expect("Could not parse code array"); 36 | evm.genesis.add_account( 37 | "0x6c249452ee469d839942e05b8492dbb9f9c70ac".into(), 38 | Account::new( 39 | 0xAABBCCDDusize.into(), 40 | Some(code.into()), 41 | 0x1.into(), 42 | Some(hashmap!{ 43 | 0x0.into() => "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 44 | }), 45 | ), 46 | ); 47 | 48 | evm 49 | } 50 | 51 | #[test] 52 | fn multiple_transactions_test() { 53 | let evm = setup_evm(); 54 | let input = EvmInput { 55 | value: 0.into(), 56 | input_data: Bytes::from_hex_str("e9ca826c000000800001020800000000000000008000000000000000000000001000000000000000000000000000000000000010101010101010100010110001000000000100000001012001010101010208010480082000401800120001080402080082040802001402080408080002004040210011010208202020084001020201040220042000041040000280800202808001018001").expect("Could not parse input"), 57 | sender: "0xdfa72de72f96cf5b127b070e90d68ec9710797c".into(), 58 | receiver: "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 59 | gas: 10_000, 60 | }; 61 | let evm = evm 62 | .execute(input) 63 | .into_evm_updated() 64 | .expect("Could not update evm"); 65 | 66 | // check storage overwritten 67 | assert_eq!( 68 | evm.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].storage 69 | [&0x00.into()], 70 | "0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into() 71 | ); 72 | 73 | // check values not changed 74 | assert_eq!( 75 | evm.genesis.alloc[&"0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into()].balance, 76 | 0x0.into(), 77 | ); 78 | assert_eq!( 79 | evm.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].balance, 80 | 0x0.into(), 81 | ); 82 | assert_eq!( 83 | evm.genesis.alloc[&"0x6c249452ee469d839942e05b8492dbb9f9c70ac".into()].balance, 84 | 0xAABBCCDDusize.into(), 85 | ); 86 | 87 | // check nonces updated 88 | assert_eq!( 89 | evm.genesis.alloc[&"0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into()].nonce, 90 | 0x2.into(), 91 | ); 92 | assert_eq!( 93 | evm.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].nonce, 94 | 0x1.into(), 95 | ); 96 | assert_eq!( 97 | evm.genesis.alloc[&"0x6c249452ee469d839942e05b8492dbb9f9c70ac".into()].nonce, 98 | 0x1.into(), 99 | ); 100 | 101 | let input = EvmInput { 102 | value: 0.into(), 103 | input_data: Bytes::from_hex_str("7c52e3250000000000081000000002000dfa72de72f96cf5b127b070e90d68ec9710797c00000000000000000000000000000000000008000100040008018008204001014010020410080202010408010201010180010101200101010201010240401802040010101010000001008000000000001000000018040000202000010000000001000000").expect("Could not parse input"), 104 | sender: "0xdfa72de72f96cf5b127b070e90d68ec9710797c".into(), 105 | receiver: "0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into(), 106 | gas: 10_000, 107 | }; 108 | let exe = evm.execute(input); 109 | let evm = exe.into_evm_updated().expect("Could not update env"); 110 | 111 | // check values 112 | assert_eq!( 113 | evm.genesis.alloc[&"0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into()].balance, 114 | 0xAABBCCDDusize.into(), 115 | ); 116 | assert_eq!( 117 | evm.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].balance, 118 | 0x0.into(), 119 | ); 120 | assert_eq!( 121 | evm.genesis.alloc[&"0x6c249452ee469d839942e05b8492dbb9f9c70ac".into()].balance, 122 | 0x0.into(), 123 | ); 124 | 125 | // check nonces updated 126 | assert_eq!( 127 | evm.genesis.alloc[&"0x0dfa72de72f96cf5b127b070e90d68ec9710797c".into()].nonce, 128 | 0x3.into(), 129 | ); 130 | assert_eq!( 131 | evm.genesis.alloc[&"0x0ad62f08b3b9f0ecc7251befbeff80c9bb488fe9".into()].nonce, 132 | 0x1.into(), 133 | ); 134 | assert_eq!( 135 | evm.genesis.alloc[&"0x6c249452ee469d839942e05b8492dbb9f9c70ac".into()].nonce, 136 | 0x1.into(), 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /examples/control_flow_hijack/control.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | contract hijack { 4 | function hijack_fn(address to) { 5 | to.delegatecall(msg.data); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/control_flow_hijack/control.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 3 | balance: 0x0 4 | nonce: 0x1000000 5 | code: 608060405260043610603e5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633d9ba18a81146043575b600080fd5b348015604e57600080fd5b50606e73ffffffffffffffffffffffffffffffffffffffff600435166070565b005b8073ffffffffffffffffffffffffffffffffffffffff1660003660405180838380828437820191505092505050600060405180830381855af4505050505600a165627a7a723058204a53cc45907b47b1b978b63fd913f36c1da3477d58d2f60ea4209d68560c69710029 6 | 7 | victim: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 8 | -------------------------------------------------------------------------------- /examples/parity_reduced/reduced_parity.sol: -------------------------------------------------------------------------------- 1 | contract MultiOwned { 2 | uint public m_numOwners; 3 | uint public m_required; 4 | uint[256] m_owners; 5 | 6 | mapping(uint => uint) m_ownerIndex; 7 | mapping(bytes32 => PendingState) m_pending; 8 | 9 | bytes32[] m_pendingIndex; 10 | 11 | struct PendingState { 12 | uint yetNeeded; 13 | uint ownersDone; 14 | uint index; 15 | } 16 | 17 | modifier onlymanyowners(bytes32 _op) { 18 | if (confirmAndCheck(_op)) _; 19 | } 20 | 21 | function confirmAndCheck(bytes32 _op) internal returns (bool) { 22 | uint ownerIndex = m_ownerIndex[uint(msg.sender)]; 23 | if (ownerIndex == 0) return; 24 | 25 | var pending = m_pending[_op]; 26 | if (pending.yetNeeded == 0) { 27 | pending.yetNeeded = m_required; 28 | pending.ownersDone = 0; 29 | pending.index = m_pendingIndex.length++; 30 | m_pendingIndex[pending.index] = _op; 31 | } 32 | 33 | uint ownerIndexBit = 2**ownerIndex; 34 | if (pending.ownersDone & ownerIndexBit == 0) { 35 | if (pending.yetNeeded <= 1) { 36 | delete m_pendingIndex[m_pending[_op].index]; 37 | delete m_pending[_op]; 38 | return true; 39 | } else { 40 | pending.yetNeeded--; 41 | pending.ownersDone |= ownerIndexBit; 42 | } 43 | } 44 | } 45 | 46 | function initMultiowned(address[] _owners, uint _required) { 47 | m_numOwners = _owners.length + 1; 48 | m_owners[1] = uint(msg.sender); 49 | m_ownerIndex[uint(msg.sender)] = 1; 50 | for (uint i = 0; i < _owners.length; ++i) 51 | { 52 | m_owners[2 + i] = uint(_owners[i]); 53 | m_ownerIndex[uint(_owners[i])] = 2 + i; 54 | } 55 | m_required = _required; 56 | } 57 | function pay(address to, uint amount) onlymanyowners(sha3(msg.data)) { 58 | to.transfer(amount); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/parity_reduced/reduced_parity.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 3 | balance: 0x10000 4 | code: 6080604052600436106100615763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416634123cb6b8114610066578063746c91711461008d578063c4076876146100a2578063c57c5f60146100d5575b600080fd5b34801561007257600080fd5b5061007b61012c565b60408051918252519081900360200190f35b34801561009957600080fd5b5061007b610132565b3480156100ae57600080fd5b506100d373ffffffffffffffffffffffffffffffffffffffff60043516602435610138565b005b3480156100e157600080fd5b50604080516020600480358082013583810280860185019096528085526100d39536959394602494938501929182918501908490808284375094975050933594506101ae9350505050565b60005481565b60015481565b60003660405180838380828437820191505092505050604051809103902061015f81610273565b156101a95760405173ffffffffffffffffffffffffffffffffffffffff84169083156108fc029084906000818181858888f193505050501580156101a7573d6000803e3d6000fd5b505b505050565b815160019081016000908155336003819055815261010260205260408120919091555b825181101561026c5782818151811015156101e857fe5b6020908102909101015173ffffffffffffffffffffffffffffffffffffffff166002828101610100811061021857fe5b0181905550806002016101026000858481518110151561023457fe5b602090810290910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020556001016101d1565b5060015550565b3360009081526101026020526040812054818082151561029257610379565b600085815261010360205260409020805490925015156102ee576001805483556000838201556101048054916102ca91908301610381565b60028301819055610104805487929081106102e157fe5b6000918252602090912001555b8260020a9050808260010154166000141561037957815460011061036657600085815261010360205260409020600201546101048054909190811061032f57fe5b6000918252602080832090910182905586825261010390526040812081815560018082018390556002909101919091559350610379565b8154600019018255600182018054821790555b505050919050565b8154818355818111156101a9576000838152602090206101a99181019083016103be91905b808211156103ba57600081556001016103a6565b5090565b905600a165627a7a72305820a3550987b20be0fac98c2fe5d76884c82d76129f0889f7c4432ac9c2a00cf9f70029 5 | nonce: 0x1000000 6 | 7 | victim: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 8 | -------------------------------------------------------------------------------- /examples/rubixi/rubixi.sol: -------------------------------------------------------------------------------- 1 | 2 | contract Rubixi { 3 | 4 | //Declare variables for storage critical to contract 5 | uint private balance = 0; 6 | uint private collectedFees = 0; 7 | uint private feePercent = 10; 8 | uint private pyramidMultiplier = 300; 9 | uint private payoutOrder = 0; 10 | 11 | address private creator; 12 | 13 | //Sets creator 14 | function DynamicPyramid() { 15 | creator = msg.sender; 16 | } 17 | 18 | modifier onlyowner { 19 | if (msg.sender == creator) _; 20 | } 21 | 22 | struct Participant { 23 | address etherAddress; 24 | uint payout; 25 | } 26 | 27 | Participant[] private participants; 28 | 29 | //Fallback function 30 | function() payable { 31 | init(); 32 | } 33 | 34 | //init function run on fallback 35 | function init() private { 36 | //Ensures only tx with value of 1 ether or greater are processed and added to pyramid 37 | if (msg.value < 1 ether) { 38 | collectedFees += msg.value; 39 | return; 40 | } 41 | 42 | uint _fee = feePercent; 43 | //50% fee rebate on any ether value of 50 or greater 44 | if (msg.value >= 50 ether) _fee /= 2; 45 | 46 | addPayout(_fee); 47 | } 48 | 49 | //Function called for valid tx to the contract 50 | function addPayout(uint _fee) private { 51 | //Adds new address to participant array 52 | participants.push(Participant(msg.sender, (msg.value * pyramidMultiplier) / 100)); 53 | 54 | //These statements ensure a quicker payout system to later pyramid entrants, so the pyramid has a longer lifespan 55 | if (participants.length == 10) pyramidMultiplier = 200; 56 | else if (participants.length == 25) pyramidMultiplier = 150; 57 | 58 | // collect fees and update contract balance 59 | balance += (msg.value * (100 - _fee)) / 100; 60 | collectedFees += (msg.value * _fee) / 100; 61 | 62 | //Pays earlier participiants if balance sufficient 63 | while (balance > participants[payoutOrder].payout) { 64 | uint payoutToSend = participants[payoutOrder].payout; 65 | participants[payoutOrder].etherAddress.send(payoutToSend); 66 | 67 | balance -= participants[payoutOrder].payout; 68 | payoutOrder += 1; 69 | } 70 | } 71 | 72 | //Fee functions for creator 73 | function collectAllFees() onlyowner { 74 | if (collectedFees == 0) throw; 75 | 76 | creator.send(collectedFees); 77 | collectedFees = 0; 78 | } 79 | 80 | function collectFeesInEther(uint _amt) onlyowner { 81 | _amt *= 1 ether; 82 | if (_amt > collectedFees) collectAllFees(); 83 | 84 | if (collectedFees == 0) throw; 85 | 86 | creator.send(_amt); 87 | collectedFees -= _amt; 88 | } 89 | 90 | function collectPercentOfFees(uint _pcent) onlyowner { 91 | if (collectedFees == 0 || _pcent > 100) throw; 92 | 93 | uint feesToCollect = collectedFees / 100 * _pcent; 94 | creator.send(feesToCollect); 95 | collectedFees -= feesToCollect; 96 | } 97 | 98 | //Functions for changing variables related to the contract 99 | function changeOwner(address _owner) onlyowner { 100 | creator = _owner; 101 | } 102 | 103 | function changeMultiplier(uint _mult) onlyowner { 104 | if (_mult > 300 || _mult < 120) throw; 105 | 106 | pyramidMultiplier = _mult; 107 | } 108 | 109 | function changeFeePercentage(uint _fee) onlyowner { 110 | if (_fee > 10) throw; 111 | 112 | feePercent = _fee; 113 | } 114 | 115 | //Functions to provide information to end-user using JSON interface or other interfaces 116 | function currentMultiplier() constant returns(uint multiplier, string info) { 117 | multiplier = pyramidMultiplier; 118 | info = 'This multiplier applies to you as soon as transaction is received, may be lowered to hasten payouts or increased if payouts are fast enough. Due to no float or decimals, multiplier is x100 for a fractional multiplier e.g. 250 is actually a 2.5x multiplier. Capped at 3x max and 1.2x min.'; 119 | } 120 | 121 | function currentFeePercentage() constant returns(uint fee, string info) { 122 | fee = feePercent; 123 | info = 'Shown in % form. Fee is halved(50%) for amounts equal or greater than 50 ethers. (Fee may change, but is capped to a maximum of 10%)'; 124 | } 125 | 126 | function currentPyramidBalanceApproximately() constant returns(uint pyramidBalance, string info) { 127 | pyramidBalance = balance / 1 ether; 128 | info = 'All balance values are measured in Ethers, note that due to no decimal placing, these values show up as integers only, within the contract itself you will get the exact decimal value you are supposed to'; 129 | } 130 | 131 | function nextPayoutWhenPyramidBalanceTotalsApproximately() constant returns(uint balancePayout) { 132 | balancePayout = participants[payoutOrder].payout / 1 ether; 133 | } 134 | 135 | function feesSeperateFromBalanceApproximately() constant returns(uint fees) { 136 | fees = collectedFees / 1 ether; 137 | } 138 | 139 | function totalParticipants() constant returns(uint count) { 140 | count = participants.length; 141 | } 142 | 143 | function numberOfParticipantsWaitingForPayout() constant returns(uint count) { 144 | count = participants.length - payoutOrder; 145 | } 146 | 147 | function participantDetails(uint orderInPyramid) constant returns(address Address, uint Payout) { 148 | if (orderInPyramid <= participants.length) { 149 | Address = participants[orderInPyramid].etherAddress; 150 | Payout = participants[orderInPyramid].payout / 1 ether; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /examples/rubixi/rubixi.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a: 3 | balance: 0x000000000000000000000000000000000000000000000005e1bd6f2471e20000 4 | nonce: 0x1000000 5 | code: 6080604052600436106100da5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166309dfdc7181146100e4578063253459e3146101785780634229616d1461019f57806357d4021b146101b757806367f809e9146101cc578063686f2c90146101e15780636fbaaa1e146101f65780638a5fb3ca1461020b5780639dbc4f9b14610220578063a26dbf261461025b578063a6f9dae114610270578063b402295014610291578063ced92670146102a9578063d11f13df146102c1578063fae14192146102d6575b6100e26102ee565b005b3480156100f057600080fd5b506100f9610332565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561013c578181015183820152602001610124565b50505050905090810190601f1680156101695780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561018457600080fd5b5061018d610367565b60408051918252519081900360200190f35b3480156101ab57600080fd5b506100e260043561037e565b3480156101c357600080fd5b5061018d6103f0565b3480156101d857600080fd5b506100e2610427565b3480156101ed57600080fd5b506100e2610448565b34801561020257600080fd5b506100f961049c565b34801561021757600080fd5b506100f96104c3565b34801561022c57600080fd5b506102386004356104e8565b60408051600160a060020a03909316835260208301919091528051918290030190f35b34801561026757600080fd5b5061018d61055e565b34801561027c57600080fd5b506100e2600160a060020a0360043516610564565b34801561029d57600080fd5b506100e26004356105a4565b3480156102b557600080fd5b506100e2600435610619565b3480156102cd57600080fd5b5061018d61064b565b3480156102e257600080fd5b506100e2600435610655565b6000670de0b6b3a764000034101561030d57600180543401905561032f565b506002546802b5e3af16b1880000341061032657600290045b61032f8161067b565b50565b60008054606090670de0b6b3a7640000900491506101006040519081016040528060ca815260200161089960ca913990509091565b600154600090670de0b6b3a7640000905b04905090565b600554600090600160a060020a03163314156103ec5760015415806103a35750606482115b156103ad57600080fd5b506001546005546040516064909204830291600160a060020a03909116906108fc8315029083906000818181858888f150506001805485900390555050505b5050565b6000670de0b6b3a7640000600660045481548110151561040c57fe5b90600052602060002090600202016001015481151561037857fe5b6005805473ffffffffffffffffffffffffffffffffffffffff191633179055565b600554600160a060020a031633141561049a57600154151561046957600080fd5b600554600154604051600160a060020a039092169181156108fc0291906000818181858888f1505060006001555050505b565b60035460408051610140810190915261011f80825260609190610963602083013990509091565b6002546040805160c08101909152608480825260609190610815602083013990509091565b6006546000908190831161055957600680548490811061050457fe5b600091825260209091206002909102015460068054600160a060020a039092169350670de0b6b3a7640000918590811061053a57fe5b90600052602060002090600202016001015481151561055557fe5b0490505b915091565b60065490565b600554600160a060020a031633141561032f5760058054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff1990911617905550565b600554600160a060020a031633141561032f57670de0b6b3a7640000810290506001548111156105d6576105d6610448565b60015415156105e457600080fd5b600554604051600160a060020a039091169082156108fc029083906000818181858888f1505060018054859003905550505050565b600554600160a060020a031633141561032f5761012c81118061063c5750607881105b1561064657600080fd5b600355565b6004546006540390565b600554600160a060020a031633141561032f57600a81111561067657600080fd5b600255565b60006006604080519081016040528033600160a060020a03168152602001606460035434028115156106a957fe5b04905281546001808201845560009384526020938490208351600290930201805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039093169290921782559190920151910155600654600a141561070f5760c8600355610720565b600654601914156107205760966003555b60008054606434858203810282900490920190925560018054918502929092040190555b600660045481548110151561075557fe5b90600052602060002090600202016001015460005411156103ec57600660045481548110151561078157fe5b906000526020600020906002020160010154905060066004548154811015156107a657fe5b60009182526020822060029091020154604051600160a060020a039091169183156108fc02918491818181858888f193505050505060066004548154811015156107ec57fe5b6000918252602082206001600290920201810154825403909155600480549091019055610744560053686f776e20696e202520666f726d2e204665652069732068616c766564283530252920666f7220616d6f756e747320657175616c206f722067726561746572207468616e203530206574686572732e2028466565206d6179206368616e67652c206275742069732063617070656420746f2061206d6178696d756d206f662031302529416c6c2062616c616e63652076616c75657320617265206d6561737572656420696e204574686572732c206e6f746520746861742064756520746f206e6f20646563696d616c20706c6163696e672c2074686573652076616c7565732073686f7720757020617320696e746567657273206f6e6c792c2077697468696e2074686520636f6e747261637420697473656c6620796f752077696c6c206765742074686520657861637420646563696d616c2076616c756520796f752061726520737570706f73656420746f54686973206d756c7469706c696572206170706c69657320746f20796f7520617320736f6f6e206173207472616e73616374696f6e2069732072656365697665642c206d6179206265206c6f776572656420746f2068617374656e207061796f757473206f7220696e63726561736564206966207061796f75747320617265206661737420656e6f7567682e2044756520746f206e6f20666c6f6174206f7220646563696d616c732c206d756c7469706c696572206973207831303020666f722061206672616374696f6e616c206d756c7469706c69657220652e672e203235302069732061637475616c6c79206120322e3578206d756c7469706c6965722e20436170706564206174203378206d617820616e6420312e3278206d696e2ea165627a7a7230582065bc9ed1f44a06d0e3658fc6b86e512f6d018b6962dfda4647131b6a68e2dbaa0029 6 | storage: 7 | 0x0: 0x000000000000000000000000000000000000000000000005e1bd6f2471e20000 # balance 8 | 0x1: 0x0000000000000000000000000000000000000000000000005a34a38fc00a0000 # collected fees 9 | 0x2: 0x0a # fee Percentage 10 | 0x3: 0x012c # pyramid multiplier 11 | # 0x4 set to 0 12 | # 0x5 mistakenly not set by the contract (this is the owner variable) 13 | 0x6: 0x0000000000000000000000000000000000000000000000000000000000000004 # array length 14 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d44: 0x0821ab0d4414980000 15 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d40: 0x0821ab0d4414980000 16 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d41: 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db 17 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d43: 0x583031d1113ad414f02576bd6afabfb302140225 18 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d42: 0x01a055690d9db80000 19 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f: 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c 20 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d45: 0x000000000000000000000000dd870fa1b7c4700f2bd7f44238821c26f7392148 21 | 0xf652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d46: 0x000000000000000000000000000000000000000000000000d02ab486cedc0000 22 | 23 | victim: 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a 24 | -------------------------------------------------------------------------------- /examples/unprotected_function/parity_bug_call.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract ParityWalletBugCall { 4 | address owner; 5 | 6 | modifier onlyOwner { 7 | assert(msg.sender == owner); 8 | _; 9 | } 10 | 11 | function _ParityWalletBug() { 12 | owner = msg.sender; 13 | } 14 | 15 | function send() onlyOwner public { 16 | owner.send(address(this).balance); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/unprotected_function/parity_bug_call_with_arg.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract ParityWalletBugCallArg { 4 | address owner; 5 | 6 | modifier onlyOwner { 7 | assert(msg.sender == owner); 8 | _; 9 | } 10 | 11 | function _ParityWalletBug() { 12 | owner = msg.sender; 13 | } 14 | 15 | function send(address _to) onlyOwner public { 16 | _to.send(address(this).balance); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/unprotected_function/parity_bug_suicide.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract ParityWalletBugSuicide { 4 | address owner; 5 | 6 | modifier onlyOwner { 7 | assert(msg.sender == owner); 8 | _; 9 | } 10 | 11 | function _ParityWalletBug() { 12 | owner = msg.sender; 13 | } 14 | 15 | function kill() onlyOwner public { 16 | selfdestruct(owner); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/unprotected_function/unprotected_call.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 3 | balance: 0x100000 4 | nonce: 0x1000000 5 | code: 60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632f432c0814610051578063b46300ec14610066575b600080fd5b341561005c57600080fd5b61006461007b565b005b341561007157600080fd5b6100796100bd565b005b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561011557fe5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f19350505050505600a165627a7a72305820d75c6761a4d0d06e3c46331d51fdfbba10399d6e07f1db08a7d76d01bce893b60029 6 | storage: 7 | 0x0: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 8 | 9 | 10 | victim: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 11 | -------------------------------------------------------------------------------- /examples/unprotected_function/unprotected_call_args.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 3 | balance: 0x100000 4 | nonce: 0x1000000 5 | code: 60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632f432c08146100515780633e58c58c14610066575b600080fd5b341561005c57600080fd5b61006461009f565b005b341561007157600080fd5b61009d600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100e1565b005b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561013957fe5b8073ffffffffffffffffffffffffffffffffffffffff166108fc3073ffffffffffffffffffffffffffffffffffffffff16319081150290604051600060405180830381858888f1935050505050505600a165627a7a7230582036758e41dfbec0634f82e1211f57a0c08942a965fd8b7afa606e21b2fd109c3b0029 6 | storage: 7 | 0x0: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 8 | 9 | 10 | victim: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 11 | -------------------------------------------------------------------------------- /examples/unprotected_function/unprotected_suicide.yml: -------------------------------------------------------------------------------- 1 | state: 2 | 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9: 3 | balance: 0x100000 4 | nonce: 0x1000000 5 | code: 60606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632f432c081461005157806341c0e1b514610066575b600080fd5b341561005c57600080fd5b61006461007b565b005b341561007157600080fd5b6100796100bd565b005b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561011557fe5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff00a165627a7a72305820f3144583596e67d998d568c2e75c63dd961472f8f27d9fb5bad677c80d5d3dd30029 6 | storage: 7 | 0x0: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 8 | 9 | 10 | victim: 0xaad62f08b3b9f0ecc7251befbeff80c9bb488fe9 11 | -------------------------------------------------------------------------------- /media/ethfant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RUB-SysSec/EthBMC/e887f33fff98a31440b0d7acd86bbeec10841d45/media/ethfant.png -------------------------------------------------------------------------------- /parity_connector/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parity_connector" 3 | version = "0.1.0" 4 | authors = [""] 5 | 6 | [dependencies] 7 | jsonrpc-client-core = "0.5.0" 8 | jsonrpc-client-http = "0.5.0" 9 | clap = "2.32.0" 10 | futures = "0.1.25" 11 | serde = "1.0.79" 12 | serde_json = "1.0.32" 13 | serde_derive = "1.0.79" 14 | serde_test = "1.0.79" 15 | ethereum-newtypes = { path = "../evmexec/ethereum-newtypes" } 16 | 17 | [dependencies.uint] 18 | version = "0.4" 19 | features = ["std"] 20 | 21 | [lib] 22 | path = "src/lib.rs" 23 | 24 | [[bin]] 25 | path = "src/main.rs" 26 | name = "paritycli" 27 | -------------------------------------------------------------------------------- /parity_connector/src/client.rs: -------------------------------------------------------------------------------- 1 | use ethereum_newtypes::{Address, Bytes, WU256}; 2 | 3 | use types::*; 4 | 5 | jsonrpc_client!( 6 | #[derive(Clone, Debug)] 7 | pub struct ParityClient { 8 | /// Returns the current blocknumber 9 | pub fn eth_blockNumber(&mut self) -> RpcRequest; 10 | 11 | /// Returns the current coinbase 12 | pub fn eth_getBlockByNumber(&mut self, number: BlockSelector, as_tx: bool) -> RpcRequest; 13 | 14 | /// Load the code for a given address and block number 15 | pub fn eth_getCode(&mut self, address: Address, number: BlockSelector) -> RpcRequest; 16 | 17 | /// Load the balance of a given account at the given blocknumber 18 | pub fn eth_getBalance(&mut self, address: Address, number: BlockSelector) -> RpcRequest; 19 | 20 | pub fn eth_getStorage(&mut self, address: Address, number: BlockSelector) -> RpcRequest; 21 | }); 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::ParityClient; 26 | 27 | use jsonrpc_client_core::Transport; 28 | use serde_json as serde; 29 | use serde_json::Value as JsonValue; 30 | use std::io; 31 | use uint::U256; 32 | 33 | use futures::future as futures; 34 | use futures::future::Future; 35 | 36 | type BoxFuture = Box + Send>; 37 | 38 | struct ParityTestTransport; 39 | 40 | impl Transport for ParityTestTransport { 41 | type Future = BoxFuture, io::Error>; 42 | type Error = io::Error; 43 | 44 | fn get_next_id(&mut self) -> u64 { 45 | 1 46 | } 47 | 48 | fn send(&self, json_data: Vec) -> Self::Future { 49 | let data = serde::from_slice::(&json_data).unwrap(); 50 | 51 | let json = match data["method"] { 52 | serde::Value::String(ref s) if s == "eth_blockNumber" => json!({ 53 | "jsonrpc": "2.0", 54 | "id": 1, 55 | "result": "0x5c9728", 56 | }), 57 | _ => unreachable!(), 58 | }; 59 | Box::new(futures::ok(serde::to_vec(&json).unwrap())) 60 | } 61 | } 62 | 63 | #[test] 64 | fn eth_block_number_test() { 65 | let mut client = ParityClient::new(ParityTestTransport); 66 | let res = client.eth_blockNumber().call().unwrap(); 67 | assert_eq!(res.0, U256::from_dec_str("6068008").unwrap()); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /parity_connector/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate ethereum_newtypes; 2 | extern crate futures; 3 | extern crate uint; 4 | 5 | extern crate serde; 6 | #[macro_use] 7 | extern crate serde_derive; 8 | #[cfg_attr(test, macro_use)] 9 | extern crate serde_json; 10 | 11 | #[cfg(test)] 12 | extern crate serde_test; 13 | 14 | #[macro_use] 15 | extern crate jsonrpc_client_core; 16 | extern crate jsonrpc_client_http; 17 | 18 | #[macro_use] 19 | mod macros; 20 | mod client; 21 | mod types; 22 | 23 | pub use types::{Block, BlockSelector}; 24 | 25 | use jsonrpc_client_http::{HttpHandle, HttpTransport}; 26 | use uint::U256; 27 | 28 | use client::ParityClient; 29 | 30 | /// Creates parity http client 31 | pub fn create_client(ip: &str, port: isize) -> ParityConnector { 32 | ParityConnector::new(ip, port) 33 | } 34 | 35 | #[derive(Clone, Debug)] 36 | pub struct ParityConnector { 37 | client: ParityClient, 38 | } 39 | 40 | impl ParityConnector { 41 | pub fn new(ip: &str, port: isize) -> Self { 42 | let transport = HttpTransport::new().standalone().unwrap(); 43 | let transport_handle = transport 44 | .handle(&format!("http://{}:{}", ip, port)) 45 | .unwrap(); 46 | let client = ParityClient::new(transport_handle); 47 | ParityConnector { client } 48 | } 49 | 50 | pub fn blocknumber(&mut self) -> U256 { 51 | self.client.eth_blockNumber().call().unwrap().0 52 | } 53 | 54 | pub fn block_by_number(&mut self, number: BlockSelector) -> Block { 55 | self.client 56 | .eth_getBlockByNumber(number, false) 57 | .call() 58 | .unwrap() 59 | } 60 | 61 | pub fn code(&mut self, addr: U256, block: BlockSelector) -> Vec { 62 | self.client 63 | .eth_getCode(addr.into(), block) 64 | .call() 65 | .unwrap() 66 | .0 67 | } 68 | 69 | pub fn balance(&mut self, addr: U256, block: BlockSelector) -> U256 { 70 | self.client 71 | .eth_getBalance(addr.into(), block) 72 | .call() 73 | .unwrap() 74 | .0 75 | } 76 | 77 | pub fn storage(&mut self, addr: U256, block: BlockSelector) -> Option> { 78 | let stor = self.client.eth_getStorage(addr.into(), block).call().ok()?; 79 | Some( 80 | stor.0 81 | .into_iter() 82 | .map(|v| v.into_iter()) 83 | .map(|mut v| (v.next().unwrap().0, v.next().unwrap().0)) 84 | .collect(), 85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /parity_connector/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! implement_return_struct { 3 | // capture members as ident and tt see: 4 | // https://danielkeep.github.io/tlborm/book/mbe-min-captures-and-expansion-redux.html 5 | ( 6 | $(#[$struct_attr:meta])* 7 | pub struct $name:ident { 8 | $( $field_name:ident: $type:tt, )+ 9 | } 10 | ) => { 11 | #[derive(Deserialize, Debug)] 12 | $(#[$struct_attr])* 13 | pub struct $name { 14 | $( $field_name: $type, )+ 15 | } 16 | 17 | impl $name { 18 | $( impl_getter!($field_name : $type); )+ 19 | } 20 | }; 21 | ( 22 | $(#[$struct_attr:meta])* 23 | struct $name:ident { 24 | $( $field_name:ident: $type:tt, )+ 25 | } 26 | ) => { 27 | #[derive(Deserialize, Debug)] 28 | $(#[$struct_attr])* 29 | struct $name { 30 | $( $field_name: $type, )+ 31 | } 32 | 33 | impl $name { 34 | $( impl_getter!(pub $field_name : $type); )+ 35 | } 36 | }; 37 | } 38 | 39 | #[allow(unused_macros)] // because reasons 40 | macro_rules! impl_getter { 41 | // SerializableU256 to U256 42 | (pub $field_name:ident : SerializableU256) => { 43 | pub fn $field_name(&self) -> &U256 { 44 | self.$field_name.as_ref() 45 | } 46 | }; 47 | ($field_name:ident : SerializableU256) => { 48 | pub fn $field_name(&self) -> &U256 { 49 | self.$field_name.as_ref() 50 | } 51 | }; 52 | // fallback 53 | ($field_name:ident : $type:ty) => { 54 | pub fn $field_name(&self) -> &$type { 55 | &self.$field_name 56 | } 57 | }; 58 | (pub $field_name:ident : $type:ty) => { 59 | pub fn $field_name(&self) -> &$type { 60 | &self.$field_name 61 | } 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /parity_connector/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate parity_connector; 3 | extern crate uint; 4 | 5 | use clap::{App, Arg, ArgMatches}; 6 | use parity_connector::{create_client, BlockSelector}; 7 | use uint::U256; 8 | 9 | fn main() { 10 | let matches = parse_args(); 11 | 12 | let ip = matches.value_of("IP").unwrap(); 13 | let port = matches 14 | .value_of("port") 15 | .unwrap_or("8545") 16 | .parse::() 17 | .unwrap(); 18 | 19 | let mut client = create_client(ip, port); 20 | let result1 = client.blocknumber(); 21 | let res2 = client.block_by_number(BlockSelector::Specific(4_649_480)); 22 | let res3 = client.storage( 23 | U256::from_dec_str("121489304910085681030520874682045790491262858723").unwrap(), 24 | BlockSelector::Specific(4_649_480), 25 | ); 26 | 27 | println!("{:?}", result1); 28 | println!("{:?}", res2); 29 | println!("{:?}", res2.difficulty()); 30 | println!("{:?}", res3); 31 | 32 | let res = client.code( 33 | U256::from_dec_str("1130997971043064165214724645812216908397186898329").unwrap(), 34 | BlockSelector::Latest, 35 | ); 36 | println!("{:?}", res); 37 | 38 | let res = client.storage( 39 | U256::from_dec_str("1130997971043064165214724645812216908397186898329").unwrap(), 40 | BlockSelector::Latest, 41 | ); 42 | println!("{:?}", res); 43 | } 44 | 45 | fn parse_args<'a>() -> ArgMatches<'a> { 46 | App::new("Parity ClI") 47 | .version("0.1.0") 48 | .about("Simple Cli client for testing parity") 49 | .arg( 50 | Arg::with_name("IP") 51 | .required(true) 52 | .index(1) 53 | .help("Ip address of the running parity jsonrpc server"), 54 | ) 55 | .arg( 56 | Arg::with_name("port") 57 | .short("p") 58 | .long("port") 59 | .help("port number standard: 8545") 60 | .takes_value(true), 61 | ) 62 | .get_matches() 63 | } 64 | -------------------------------------------------------------------------------- /parity_connector/src/types.rs: -------------------------------------------------------------------------------- 1 | use ethereum_newtypes::WU256; 2 | use serde::ser::{Serialize, Serializer}; 3 | 4 | /// An enum to select the block to fetch from the blockchain 5 | #[derive(Clone, Copy, Debug, Deserialize)] 6 | pub enum BlockSelector { 7 | Earliest, 8 | Latest, 9 | Pending, 10 | Specific(usize), 11 | } 12 | 13 | impl Serialize for BlockSelector { 14 | fn serialize(&self, serializer: S) -> Result 15 | where 16 | S: Serializer, 17 | { 18 | match self { 19 | BlockSelector::Earliest => serializer.serialize_str("earliest"), 20 | BlockSelector::Latest => serializer.serialize_str("latest"), 21 | BlockSelector::Pending => serializer.serialize_str("pending"), 22 | BlockSelector::Specific(u) => serializer.serialize_str(&format!("0x{:x}", u)), 23 | } 24 | } 25 | } 26 | 27 | /// A rust representation of an ethereum block, reduced to the fields used by ethAEG 28 | implement_return_struct!( 29 | #[serde(rename_all = "camelCase")] 30 | pub struct Block { 31 | gas_limit: WU256, 32 | number: WU256, 33 | miner: WU256, 34 | hash: WU256, 35 | timestamp: WU256, 36 | difficulty: WU256, 37 | } 38 | ); 39 | 40 | /// A rust representation of an account storaage 41 | #[derive(Debug, Serialize, Deserialize)] 42 | pub struct Storage(pub Vec>); 43 | -------------------------------------------------------------------------------- /run_all_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "# ==================================================" 4 | echo "# Running Unit Tests" 5 | echo "# ==================================================" 6 | 7 | cargo test -- --test-threads=1 8 | 9 | echo "# ==================================================" 10 | echo "# Running Expensive & Parity Dependent Tests" 11 | echo "# ==================================================" 12 | 13 | cargo test se --release -- --ignored --test-threads=1 14 | 15 | echo "# ==================================================" 16 | echo "# Running Integration Tests" 17 | echo "# ==================================================" 18 | 19 | cargo test integra --release -- --ignored 20 | --------------------------------------------------------------------------------