├── .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 Systems Security
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 |
--------------------------------------------------------------------------------