From baff14bd5dbb4062e2ef56784fb6e84319c72970 Mon Sep 17 00:00:00 2001 From: Wei Tang Date: Mon, 3 Apr 2017 11:31:24 +0800 Subject: [PATCH] Problem: initial commit Solution: cherry-pick and refactor --- AUTHORS | 8 + CONTRIBUTING.md | 122 +++++++++++++++ Cargo.toml | 20 +++ GUIDE.md | 306 +++++++++++++++++++++++++++++++++++++ README.md | 36 +++++ default.nix | 64 ++++++++ shell.nix | 1 + src/bin/cli.rs | 79 ++++++++++ src/lib.rs | 6 + src/vm/gas.rs | 42 ++++++ src/vm/machine.rs | 43 ++++++ src/vm/memory.rs | 47 ++++++ src/vm/mod.rs | 20 +++ src/vm/opcode/code.rs | 333 +++++++++++++++++++++++++++++++++++++++++ src/vm/opcode/mod.rs | 5 + src/vm/opcode/run.rs | 73 +++++++++ src/vm/opcode/usage.rs | 123 +++++++++++++++ src/vm/pc.rs | 51 +++++++ src/vm/stack.rs | 45 ++++++ 19 files changed, 1424 insertions(+) create mode 100644 AUTHORS create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.toml create mode 100644 GUIDE.md create mode 100644 README.md create mode 100644 default.nix create mode 100644 shell.nix create mode 100644 src/bin/cli.rs create mode 100644 src/lib.rs create mode 100644 src/vm/gas.rs create mode 100644 src/vm/machine.rs create mode 100644 src/vm/memory.rs create mode 100644 src/vm/mod.rs create mode 100644 src/vm/opcode/code.rs create mode 100644 src/vm/opcode/mod.rs create mode 100644 src/vm/opcode/run.rs create mode 100644 src/vm/opcode/usage.rs create mode 100644 src/vm/pc.rs create mode 100644 src/vm/stack.rs diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..f6594347 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,8 @@ +Corporate Copyright Statements +============================== + +Individual Copyright Statements +=============================== + +Stewart Mackenzie +Wei Tang \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..83635b40 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,122 @@ +# Collective Code Construction Contract (C4) + +The Collective Code Construction Contract (C4) is an evolution of the github.com [Fork + Pull Model](https://help.github.com/articles/about-pull-requests/), aimed at providing an optimal collaboration model for free software projects. This is revision 2 of the C4 specification and deprecates RFC 22. + +## License + +Copyright (c) 2009-2016 Pieter Hintjens. + +This Specification is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + +This Specification is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + +## Abstract + +C4 provides a standard process for contributing, evaluating and discussing improvements on software projects. It defines specific technical requirements for projects like a style guide, unit tests, `git` and similar platforms. It also establishes different personas for projects, with clear and distinct duties. C4 specifies a process for documenting and discussing issues including seeking consensus and clear descriptions, use of "pull requests" and systematic reviews. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](http://tools.ietf.org/html/rfc2119). + +## 1. Goals + +C4 is meant to provide a reusable optimal collaboration model for open source software projects. It has these specific goals: + +1. To maximize the scale and diversity of the community around a project, by reducing the friction for new Contributors and creating a scaled participation model with strong positive feedbacks; +1. To relieve dependencies on key individuals by separating different skill sets so that there is a larger pool of competence in any required domain; +1. To allow the project to develop faster and more accurately, by increasing the diversity of the decision making process; +1. To support the natural life cycle of project versions from experimental through to stable, by allowing safe experimentation, rapid failure, and isolation of stable code; +1. To reduce the internal complexity of project repositories, thus making it easier for Contributors to participate and reducing the scope for error; +1. To enforce collective ownership of the project, which increases economic incentive to Contributors and reduces the risk of hijack by hostile entities. + +## 2. Design + +### 2.1. Preliminaries + +1. The project SHALL use the git distributed revision control system. +1. The project SHALL be hosted on github.com or equivalent, herein called the "Platform". +1. The project SHALL use the Platform issue tracker. +1. The project SHOULD have clearly documented guidelines for code style. +1. A "Contributor" is a person who wishes to provide a patch, being a set of commits that solve some clearly identified problem. +1. A "Maintainer" is a person who merges patches to the project. Maintainers are not developers; their job is to enforce process. +1. Contributors SHALL NOT have commit access to the repository unless they are also Maintainers. +1. Maintainers SHALL have commit access to the repository. +1. Everyone, without distinction or discrimination, SHALL have an equal right to become a Contributor under the terms of this contract. + +### 2.2. Licensing and Ownership + +1. The project SHALL use a share-alike license such as the MPLv2, or a GPLv3 variant thereof (GPL, LGPL, AGPL). +1. All contributions to the project source code ("patches") SHALL use the same license as the project. +1. All patches are owned by their authors. There SHALL NOT be any copyright assignment process. +1. Each Contributor SHALL be responsible for identifying themselves in the project Contributor list. + +### 2.3. Patch Requirements + +1. Maintainers and Contributors MUST have a Platform account and SHOULD use their real names or a well-known alias. +1. A patch SHOULD be a minimal and accurate answer to exactly one identified and agreed problem. +1. A patch MUST adhere to the code style guidelines of the project if these are defined. +1. A patch MUST adhere to the "Evolution of Public Contracts" guidelines defined below. +1. A patch SHALL NOT include non-trivial code from other projects unless the Contributor is the original author of that code. +1. A patch MUST compile cleanly and pass project self-tests on at least the principle target platform. +1. A patch commit message MUST consist of a single short (less than 50 characters) line stating the problem ("Problem: ...") being solved, followed by a blank line and then the proposed solution ("Solution: ..."). +1. A "Correct Patch" is one that satisfies the above requirements. + +### 2.4. Development Process + +1. Change on the project SHALL be governed by the pattern of accurately identifying problems and applying minimal, accurate solutions to these problems. +1. To request changes, a user SHOULD log an issue on the project Platform issue tracker. +1. The user or Contributor SHOULD write the issue by describing the problem they face or observe. +1. The user or Contributor SHOULD seek consensus on the accuracy of their observation, and the value of solving the problem. +1. Users SHALL NOT log feature requests, ideas, suggestions, or any solutions to problems that are not explicitly documented and provable. +1. Thus, the release history of the project SHALL be a list of meaningful issues logged and solved. +1. To work on an issue, a Contributor SHALL fork the project repository and then work on their forked repository. +1. To submit a patch, a Contributor SHALL create a Platform pull request back to the project. +1. A Contributor SHALL NOT commit changes directly to the project. +1. If the Platform implements pull requests as issues, a Contributor MAY directly send a pull request without logging a separate issue. +1. To discuss a patch, people MAY comment on the Platform pull request, on the commit, or elsewhere. +1. To accept or reject a patch, a Maintainer SHALL use the Platform interface. +1. Maintainers SHOULD NOT merge their own patches except in exceptional cases, such as non-responsiveness from other Maintainers for an extended period (more than 1-2 days). +1. Maintainers SHALL NOT make value judgments on correct patches. +1. Maintainers SHALL merge correct patches from other Contributors rapidly. +1. Maintainers MAY merge incorrect patches from other Contributors with the goals of (a) ending fruitless discussions, (b) capturing toxic patches in the historical record, (c) engaging with the Contributor on improving their patch quality. +1. The user who created an issue SHOULD close the issue after checking the patch is successful. +1. Any Contributor who has value judgments on a patch SHOULD express these via their own patches. +1. Maintainers SHOULD close user issues that are left open without action for an uncomfortable period of time. + +### 2.5. Branches and Releases + +1. The project SHALL have one branch ("master") that always holds the latest in-progress version and SHOULD always build. +1. The project SHALL NOT use topic branches for any reason. Personal forks MAY use topic branches. +1. To make a stable release a Maintainer shall tag the repository. Stable releases SHALL always be released from the repository master. + +### 2.6. Evolution of Public Contracts + +1. All Public Contracts (APIs or protocols) SHALL be documented. +1. All Public Contracts SHOULD have space for extensibility and experimentation. +1. A patch that modifies a stable Public Contract SHOULD not break existing applications unless there is overriding consensus on the value of doing this. +1. A patch that introduces new features SHOULD do so using new names (a new contract). +1. New contracts SHOULD be marked as "draft" until they are stable and used by real users. +1. Old contracts SHOULD be deprecated in a systematic fashion by marking them as "deprecated" and replacing them with new contracts as needed. +1. When sufficient time has passed, old deprecated contracts SHOULD be removed. +1. Old names SHALL NOT be reused by new contracts. + +### 2.7. Project Administration + +1. The project founders SHALL act as Administrators to manage the set of project Maintainers. +1. The Administrators SHALL ensure their own succession over time by promoting the most effective Maintainers. +1. A new Contributor who makes correct patches, who clearly understands the project goals, and the process SHOULD be invited to become a Maintainer. +1. Administrators SHOULD remove Maintainers who are inactive for an extended period of time, or who repeatedly fail to apply this process accurately. +1. Administrators SHOULD block or ban "bad actors" who cause stress and pain to others in the project. This should be done after public discussion, with a chance for all parties to speak. A bad actor is someone who repeatedly ignores the rules and culture of the project, who is needlessly argumentative or hostile, or who is offensive, and who is unable to self-correct their behavior when asked to do so by others. + +## Further Reading + +* [Argyris' Models 1 and 2](http://en.wikipedia.org/wiki/Chris_Argyris) - the goals of C4 are consistent with Argyris' Model 2. + +* [Toyota Kata](http://en.wikipedia.org/wiki/Toyota_Kata) - covering the Improvement Kata (fixing problems one at a time) and the Coaching Kata (helping others to learn the Improvement Kata). + +## Implementations + +* The [ZeroMQ community](http://zeromq.org) uses the C4 process for many projects. +* [OSSEC](http://www.ossec.net/) [uses the C4 process](https://ossec-docs.readthedocs.org/en/latest/development/oRFC/orfc-1.html). +* The [Machinekit](http://www.machinekit.io/) community [uses the C4 process](http://www.machinekit.io/about/). diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..4ca23370 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "sputnikvm" +version = "0.1.0" +license = "Apache-2.0" +authors = ["Stewart Mackenzie ", "Wei Tang "] +description = "SputnikVM - the Ethereum Classic Virtual Machine" + +[lib] +name = "sputnikvm" +path = "./src/lib.rs" +crate-type = ["rlib", "dylib"] + +[[bin]] +name = "svm" +path = "./src/bin/cli.rs" + +[dependencies] +clap = "2.22" +log = "0.3" +u256 = "0.1" \ No newline at end of file diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 00000000..870676f2 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,306 @@ +# Rust Style Guide + +## Formatting conventions + +These formatting conventions are a work in progress, and may do anything they +like, up to and including eating your laundry. + +### Function definitions + +In Rust, one finds functions by searching for `fn [function-name]`; It's +important that you style your code so that it's very searchable in this way. + +The proper ordering and spacing is: + +```rust +[pub] [unsafe] [extern ["ABI"]] fn foo(arg1: i32, arg2: i32) -> i32 { + ... +} +``` + +Avoid comments within the signature itself. + +### Closures + +Don't put any extra spaces before the first `|` (unless the closure is prefixed +by `move`), but put a space between the second `|` and the expression of the +closure. Between the `|`s, you should use function definition syntax, however, +elide types where possible. + +Use closures without the enclosing `{}`, if possible. Add the `{}` when you +have a return type, when there are statements before the final expression, or +when you need to split it into multiple lines; examples: + +```rust +|arg1, arg2| expr + +move |arg1: i32, arg2: i32| -> i32 { expr1; expr2 } +``` + +### Indentation + +Use spaces, not tabs. + + +### Expressions + +Do not include a space between a unary op and its operand (i.e., `!x`, not +`! x`). + +Do include spaces around binary ops (i.e., `x + 1`, not `x+1`) (including `=`). + +For comparison operators, because for `T op U`, `&T op &U` is also implemented: +if you have `t: &T`, and `u: U`, prefer `*t op u` to `t op &u`. In general, +within expressions, prefer dereferencing to taking references. + +Do not include extraneous parentheses for `if` and `while` expressions. + +```rust +if true { +} +``` + +is better than + +```rust +if (true) { +} +``` + +Do include extraneous parentheses if it makes an arithmetic or logic expression +easier to understand (`(x * 15) + (y * 20)` is fine) + +### Function calls + +Do not put a space between the function name, and the opening parenthesis. + +Do not put a space between an argument, and the comma which follows. + +Do put a space between an argument, and the comma which precedes it. + +#### Single-line calls + +Do not put a space between the function name and open paren, between the open +paren and the first argument, or between the last argument and the close paren. + +Do not put a comma after the last argument. + +```rust +foo(x, y, z) +``` + +### Methods + +Follow the function rules for calling. + +#### Single-line + +Do not put any spaces around the `.`. + +```rust +x.foo().bar().baz(x, y, z); +``` + +### as + +Put spaces before and after `as`: + +```rust +let cstr = "Hi\0" as *const str as *const [u8] as *const std::os::raw::c_char; +``` + +### Tuples and tuple structs + +Write the type list as you would a parameter list to a function. + +Build a tuple or tuple struct as you would call a function. + +#### Single-line + +```rust +struct Bar(Type1, Type2); + +let x = Bar(11, 22); +let y = (11, 22, 33); +``` + +### Enums + +In the declaration, put each variant on its own line. + +Format each variant accordingly as either a `struct`, `tuple struct`, or ident, +which doesn't require special formatting. + +```rust +enum FooBar { + First(u32), + Second, + Error { + err: Box, + line: u32, + }, +} +``` + +### macro\_rules! + +Use `{}` for the full definition of the macro. + +```rust +macro_rules! foo { +} +``` + +### Doc comments + +Prefer outer doc comments (`///` or `//*`), only use inner doc comments (`//!` +and `/*!`) to write module-level documentation. + +### Attributes + +Put each attribute on its own line, indented to the indentation of its item. +In the case of inner attributes (`#!`), indent it to the inner indentation (the +indentation of the item + 1). Prefer outer attributes, where possible. + +For attributes with argument lists, format like functions. + +```rust +#[repr(C)] +#[foo(foo, bar)] +struct CRepr { + #![repr(C)] + x: f32, + y: f32, +} +``` + +### Extern crate + +`extern crate foo;` + +Use spaces around keywords, no spaces around the semi-colon. + +### Modules + +```rust +mod foo { +} +``` + +```rust +mod foo; +``` + +Use spaces around keywords and before the opening brace, no spaces around the +semi-colon. + +## Non-formatting conventions + +### Expressions + +Prefer to use Rust's expression oriented nature where possible; + +```rust +// use +let x = if y { 1 } else { 0 }; +// not +let x; +if y { + x = 1; +} else { + x = 0; +} +``` + +### Names + + * Types shall be `PascalCase`, + * Enum variants shall be `PascalCase`, + * Struct members shall be `snake_case`, + * Function and method names shall be `snake_case`, + * Local variables shall be `snake_case`, + * Macro names shall be `snake_case`, + * Constants (`const`s and immutable `static`s) shall be `SCREAMING_SNAKE_CASE`. + +### Modules + +Avoid `#[path]` annotations where possible. + +## Cargo.toml conventions + +### Formatting conventions + +Use the same line width and indentation as Rust code. + +Put a blank line between the last key-value pair in a section and the header of +the next section. Do not place a blank line between section headers and the +key-value pairs in that section, or between key-value pairs in a section. + +Sort key names alphabetically within each section, with the exception of the +`[package]` section. Put the `[package]` section at the top of the file; put +the `name` and `version` keys in that order at the top of that section, +followed by the remaining keys other than `description` in alphabetical order, +followed by the `description` at the end of that section. + +Don't use quotes around any standard key names; use bare keys. Only use quoted +keys for non-standard keys whose names require them, and avoid introducing such +key names when possible. See the [TOML +specification](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md#table) +for details. + +Put a single space both before and after the `=` between a key and value. Do +not indent any key names; start all key names at the start of a line. + +Use multi-line strings (rather than newline escape sequences) for any string +values that include multiple lines, such as the crate description. + +For array values, such as a list of authors, put the entire list on the same +line as the key, if it fits. Otherwise, use block indentation: put a newline +after the opening square bracket, indent each item by one indentation level, +put a comma after each item (including the last), and put the closing square +bracket at the start of a line by itself after the last item. + +``` +authors = [ + "A Uthor ", + "Another Author ", +] +``` + +For table values, such as a crate dependency with a path, write the entire +table using curly braces and commas on the same line as the key if it fits. If +the entire table does not fit on the same line as the key, separate it out into +a separate section with key-value pairs: + +``` +[dependencies] +crate1 = { path = "crate1", version = "1.2.3" } + +[dependencies.extremely_long_crate_name_goes_here] +path = "extremely_long_path_name_goes_right_here" +version = "4.5.6" +``` + +### Metadata conventions + +The authors list should consist of strings that each contain an author name +followed by an email address in angle brackets: `Full Name `. +It should not contain bare email addresses, or names without email addresses. +(The authors list may also include a mailing list address without an associated +name.) + +The license field must contain a valid [SPDX +expression](https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60), +using valid [SPDX license names](https://spdx.org/licenses/). (As an exception, +by widespread convention, the license field may use `/` in place of ` OR `; for +example, `MIT/Apache-2.0`.) + +The homepage field, if present, must consist of a single URL, including the +scheme (e.g. `https://example.org/`, not just `example.org`.) + +Within the description field, wrap text at 78 columns. Don't start the +description field with the name of the crate (e.g. "cratename is a ..."); just +describe the crate itself. If providing a multi-sentence description, the first +sentence should go on a line by itself and summarize the crate, like the +subject of an email or commit message; subsequent sentences can then describe +the crate in more detail. diff --git a/README.md b/README.md new file mode 100644 index 00000000..851b3d20 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# SputnikVM: Ethereum Classic Virtual Machine + +[![Build Status](https://travis-ci.org/ethereumproject/sputnikvm.svg?branch=master)](https://travis-ci.org/ethereumproject/sputnikvm) + +## Project Description + +This is separate implementation of Ethereum Virtual Machine that can +be integrated into Geth, Parity or other clients or may be be launched +as a standalone app for debugging purposes. + +## Dependencies + +Ensure you have at least `rustc 1.16.0 (30cf806ef 2017-03-10)`. Rust +1.15.0 and before is not supported. + +## Stability Status: + +- [x] Raw +- [ ] Draft +- [ ] Stable +- [ ] Deprecated +- [ ] Legacy + +## Build Instructions + +SputnikVM is written Rust. If you are not familiar with Rust please +see the +[getting started guide](https://doc.rust-lang.org/book/getting-started.html). + +``` +$ git clone git@github.com:ethereumproject/sputnikvm.git +$ cd sputnikvm +$ cargo build +$ RUST_LOG=svm cargo run --bin svm -- -g 23 -c 00 -d data +$ cargo run -- -h +``` diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..9e59d50f --- /dev/null +++ b/default.nix @@ -0,0 +1,64 @@ +{ pkgs ? ( + let + nixpkgs = import ; + pkgs_ = (nixpkgs {}); + rustOverlay = (pkgs_.fetchFromGitHub { + owner = "mozilla"; + repo = "nixpkgs-mozilla"; + rev = "e2a920faec5a9ebd6ff34abf072aacb4e0ed6f70"; + sha256 = "1lq7zg388y4wrbl165wraji9dmlb8rkjaiam9bq28n3ynsp4b6fz"; + }); + in (nixpkgs { + overlays = [ + (import (builtins.toPath "${rustOverlay}/rust-overlay.nix")) + (self: super: { + rust = { + rustc = super.rustChannels.stable.rust; + cargo = super.rustChannels.stable.cargo; + }; + rustPlatform = super.recurseIntoAttrs (super.makeRustPlatform { + rustc = super.rustChannels.stable.rust; + cargo = super.rustChannels.stable.cargo; + }); + }) + ]; + })) +}: + +with pkgs; + +let + +env = stdenv.mkDerivation { + name = "sputnikvm-env"; + buildInputs = [ + rustc cargo + ]; +}; +tests = stdenv.mkDerivation rec { + name = "tests-${version}"; + version = "0.1.0"; + src = fetchFromGitHub { + owner = "ethereumproject"; + repo = "tests"; + rev = "d2081b17e81132e72f09a44f9d823bf6cbe6c281"; + sha256 = "10n4m2jdicbbj3rz4s63g2jklj0gkckanfi35fwjbdwf68pahnkn"; + }; + installPhase = '' + mkdir $out + cp -R * $out + ''; +}; +sputnikvm = rustPlatform.buildRustPackage (rec { + name = "sputnikvm-${version}"; + version = "0.1.0"; + depsSha256 = "0b3117j13y6ijgq4zslzmxi8xbcxpz5qaxz9792nvkrca2wr4v17"; + doCheck = true; + checkPhase = '' + target/release/gaslighter --test_dir ${tests} --artefact_dir target/release/ + ''; + src = ./.; + }); +in { + inherit env sputnikvm; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..910b9222 --- /dev/null +++ b/shell.nix @@ -0,0 +1 @@ +(import ./. {}).env diff --git a/src/bin/cli.rs b/src/bin/cli.rs new file mode 100644 index 00000000..cf34148b --- /dev/null +++ b/src/bin/cli.rs @@ -0,0 +1,79 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate log; +extern crate sputnikvm; + +use sputnikvm::vm::{VectorMachine, Gas}; + +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use log::LogLevel; + +pub fn read_hex(s: &str) -> Option> { + let mut res = Vec::::new(); + + let mut cur = 0; + let mut len = 0; + for c in s.chars() { + len += 1; + let v_option = c.to_digit(16); + if v_option.is_none() { + return None; + } + let v = v_option.unwrap(); + if len == 1 { + cur += v * 16; + } else { // len == 2 + cur += v; + } + if len == 2 { + res.push(cur as u8); + cur = 0; + len = 0; + } + } + + return Some(res); +} + +fn main() { + let matches = clap_app!(svm => + (version: "0.1.0") + (author: "SputnikVM Contributors") + (about: "SputnikVM - Ethereum Classic Virtual Machine") + (@arg GAS: -g --gas +takes_value +required "Sets the gas amount") + (@arg DATA: -d --data +takes_value +required "Sets the data needed") + (@arg CODE: -c --code +takes_value +required "Sets the path to a file which contains the vm byte code") + (@arg STATS: -s --stats "Return statistics on the execution") + (@arg debug: -D --debug ... "Sets the level of debugging information") + ).get_matches(); + + let code_hex = read_hex(match matches.value_of("CODE") { + Some(c) => c, + None => "", + }); + + let code = code_hex.expect("code must be provided"); + + let initial_gas = (value_t!(matches, "GAS", isize).unwrap_or(0xff)).into(); + let data = match matches.value_of("DATA") { + Some(d) => d.as_bytes().into(), + None => "".as_bytes().into(), + }; + + let mut machine = VectorMachine::new(code.as_slice(), data, initial_gas); + match machine.fire() { + Ok(leftover_gas) => { + if log_enabled!(LogLevel::Info) { + info!("gas used: {:?}", leftover_gas); + } + }, + Err(e) => { + if log_enabled!(LogLevel::Error) { + info!("error: {:?}", e); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..10475d7e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +extern crate u256; + +#[macro_use] +extern crate log; + +pub mod vm; diff --git a/src/vm/gas.rs b/src/vm/gas.rs new file mode 100644 index 00000000..e7795609 --- /dev/null +++ b/src/vm/gas.rs @@ -0,0 +1,42 @@ +use super::{Machine, Memory, Stack}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +#[derive(Clone, Copy, Debug)] +pub struct Gas(isize); + +impl Gas { + pub fn zero() -> Gas { Gas(0) } + pub fn is_valid(&self) -> bool { self.0 >= 0 } +} + +impl From for Gas { + fn from(val: isize) -> Gas { Gas(val) } +} + +impl Add for Gas { + type Output = Gas; + + fn add(self, other: Gas) -> Gas { + Gas(self.0 + other.0) + } +} + +impl AddAssign for Gas { + fn add_assign(&mut self, other: Gas) { + self.0 += other.0 + } +} + +impl Sub for Gas { + type Output = Gas; + + fn sub(self, other: Gas) -> Gas { + Gas(self.0 - other.0) + } +} + +impl SubAssign for Gas { + fn sub_assign(&mut self, other: Gas) { + self.0 -= other.0 + } +} diff --git a/src/vm/machine.rs b/src/vm/machine.rs new file mode 100644 index 00000000..2f7c10f9 --- /dev/null +++ b/src/vm/machine.rs @@ -0,0 +1,43 @@ +use super::{Memory, VectorMemory, Stack, VectorStack, PC, Result, Gas}; + +pub type VectorMachine<'p> = Machine<'p, VectorMemory, VectorStack>; + +pub struct Machine<'p, M, S> { + available_gas: Gas, + pub pc: PC<'p>, + pub memory: M, // Contains i + pub stack: S, +} + +impl<'p, M, S> Machine<'p, M, S> where M: Memory, S: Stack { + pub fn new(code: &'p [u8], data: &[u8], available_gas: Gas) -> Self { + Self { + available_gas: available_gas, + pc: PC::new(code), + memory: M::new(), + stack: S::new(), + } + } + + pub fn available_gas(&self) -> Gas { + self.available_gas + } + + pub fn step(&mut self) -> bool { + if self.pc.is_stopped() || !self.available_gas.is_valid() { + return false; + } + + let opcode = self.pc.read_opcode(); + self.available_gas -= opcode.gas_cost_before(self); + opcode.run(self); + self.available_gas -= opcode.gas_cost_after(self); + + true + } + + pub fn fire(&mut self) -> Result { + while self.step() { } + Ok(self.available_gas) + } +} diff --git a/src/vm/memory.rs b/src/vm/memory.rs new file mode 100644 index 00000000..9bde93e5 --- /dev/null +++ b/src/vm/memory.rs @@ -0,0 +1,47 @@ +use u256::U256; + +pub trait Memory { + fn new() -> Self; + fn write(&mut self, index: U256, value: U256); + fn read(&self, index: U256) -> U256; + fn active_len(&self) -> usize; +} + +pub struct VectorMemory { + memory: Vec, +} + +impl Memory for VectorMemory { + fn new() -> VectorMemory { + VectorMemory { + memory: Vec::new(), + } + } + + fn write(&mut self, index: U256, value: U256) { + // Vector only deals with usize, so the maximum size is + // actually smaller than 2^256 + let index_u64: u64 = index.into(); + let index = index_u64 as usize; + + if self.memory.len() <= index { + self.memory.resize(index, 0.into()); + } + + self.memory[index] = value; + } + + fn read(&self, index: U256) -> U256 { + let index_u64: u64 = index.into(); + let index = index_u64 as usize; + + match self.memory.get(index) { + Some(&v) => v, + None => 0.into() + } + } + + fn active_len(&self) -> usize { + self.memory.len() + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 00000000..d1fb452a --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,20 @@ +mod stack; +mod opcode; +mod pc; +mod gas; +mod memory; +mod machine; + +pub use self::opcode::Opcode; +pub use self::memory::{Memory, VectorMemory}; +pub use self::stack::{Stack, VectorStack}; +pub use self::pc::PC; +pub use self::machine::{Machine, VectorMachine}; +pub use self::gas::Gas; + +#[derive(Debug)] +pub enum Error { + EmptyGas, +} + +pub type Result = ::std::result::Result; diff --git a/src/vm/opcode/code.rs b/src/vm/opcode/code.rs new file mode 100644 index 00000000..03ebc692 --- /dev/null +++ b/src/vm/opcode/code.rs @@ -0,0 +1,333 @@ +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Opcode { + STOP, ADD, MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, + SIGNEXTEND, + + LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, + + SHA3, + + ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD, + CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, GASPRICE, + EXTCODESIZE, EXTCODECOPY, + + BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, + + POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, + MSIZE, GAS, JUMPDEST, + + PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, + PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, + PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, + PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32, + + DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, + DUP11, DUP12, DUP13, DUP14, DUP15, DUP16, + + SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, + SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16, + + LOG0, LOG1, LOG2, LOG3, LOG4, + + CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, + + INVALID, SELFDESTRUCT +} + +impl From for Opcode { + fn from(val: u8) -> Opcode { + match val { + 0x00 => Opcode::STOP, + 0x01 => Opcode::ADD, + 0x02 => Opcode::MUL, + 0x03 => Opcode::SUB, + 0x04 => Opcode::DIV, + 0x05 => Opcode::SDIV, + 0x06 => Opcode::MOD, + 0x07 => Opcode::SMOD, + 0x08 => Opcode::ADDMOD, + 0x09 => Opcode::MULMOD, + 0x0a => Opcode::EXP, + 0x0b => Opcode::SIGNEXTEND, + + 0x10 => Opcode::LT, + 0x11 => Opcode::GT, + 0x12 => Opcode::SLT, + 0x13 => Opcode::SGT, + 0x14 => Opcode::EQ, + 0x15 => Opcode::ISZERO, + 0x16 => Opcode::AND, + 0x17 => Opcode::OR, + 0x18 => Opcode::XOR, + 0x19 => Opcode::NOT, + 0x1a => Opcode::BYTE, + + 0x20 => Opcode::SHA3, + + 0x30 => Opcode::ADDRESS, + 0x31 => Opcode::BALANCE, + 0x32 => Opcode::ORIGIN, + 0x33 => Opcode::CALLER, + 0x34 => Opcode::CALLVALUE, + 0x35 => Opcode::CALLDATALOAD, + 0x36 => Opcode::CALLDATASIZE, + 0x37 => Opcode::CALLDATACOPY, + 0x38 => Opcode::CODESIZE, + 0x39 => Opcode::CODECOPY, + 0x3a => Opcode::GASPRICE, + 0x3b => Opcode::EXTCODESIZE, + 0x3c => Opcode::EXTCODECOPY, + + 0x40 => Opcode::BLOCKHASH, + 0x41 => Opcode::COINBASE, + 0x42 => Opcode::TIMESTAMP, + 0x43 => Opcode::NUMBER, + 0x44 => Opcode::DIFFICULTY, + 0x45 => Opcode::GASLIMIT, + + 0x50 => Opcode::POP, + 0x51 => Opcode::MLOAD, + 0x52 => Opcode::MSTORE, + 0x53 => Opcode::MSTORE8, + 0x54 => Opcode::SLOAD, + 0x55 => Opcode::SSTORE, + 0x56 => Opcode::JUMP, + 0x57 => Opcode::JUMPI, + 0x58 => Opcode::PC, + 0x59 => Opcode::MSIZE, + 0x5a => Opcode::GAS, + 0x5b => Opcode::JUMPDEST, + + 0x60 => Opcode::PUSH1, + 0x61 => Opcode::PUSH2, + 0x62 => Opcode::PUSH3, + 0x63 => Opcode::PUSH4, + 0x64 => Opcode::PUSH5, + 0x65 => Opcode::PUSH6, + 0x66 => Opcode::PUSH7, + 0x67 => Opcode::PUSH8, + 0x68 => Opcode::PUSH9, + 0x69 => Opcode::PUSH10, + 0x6a => Opcode::PUSH11, + 0x6b => Opcode::PUSH12, + 0x6c => Opcode::PUSH13, + 0x6d => Opcode::PUSH14, + 0x6e => Opcode::PUSH15, + 0x6f => Opcode::PUSH16, + 0x70 => Opcode::PUSH17, + 0x71 => Opcode::PUSH18, + 0x72 => Opcode::PUSH19, + 0x73 => Opcode::PUSH20, + 0x74 => Opcode::PUSH21, + 0x75 => Opcode::PUSH22, + 0x76 => Opcode::PUSH23, + 0x77 => Opcode::PUSH24, + 0x78 => Opcode::PUSH25, + 0x79 => Opcode::PUSH26, + 0x7a => Opcode::PUSH27, + 0x7b => Opcode::PUSH28, + 0x7c => Opcode::PUSH29, + 0x7d => Opcode::PUSH30, + 0x7e => Opcode::PUSH31, + 0x7f => Opcode::PUSH32, + + 0x80 => Opcode::DUP1, + 0x81 => Opcode::DUP2, + 0x82 => Opcode::DUP3, + 0x83 => Opcode::DUP4, + 0x84 => Opcode::DUP5, + 0x85 => Opcode::DUP6, + 0x86 => Opcode::DUP7, + 0x87 => Opcode::DUP8, + 0x88 => Opcode::DUP9, + 0x89 => Opcode::DUP10, + 0x8a => Opcode::DUP11, + 0x8b => Opcode::DUP12, + 0x8c => Opcode::DUP13, + 0x8d => Opcode::DUP14, + 0x8e => Opcode::DUP15, + 0x8f => Opcode::DUP16, + + 0x90 => Opcode::SWAP1, + 0x91 => Opcode::SWAP2, + 0x92 => Opcode::SWAP3, + 0x93 => Opcode::SWAP4, + 0x94 => Opcode::SWAP5, + 0x95 => Opcode::SWAP6, + 0x96 => Opcode::SWAP7, + 0x97 => Opcode::SWAP8, + 0x98 => Opcode::SWAP9, + 0x99 => Opcode::SWAP10, + 0x9a => Opcode::SWAP11, + 0x9b => Opcode::SWAP12, + 0x9c => Opcode::SWAP13, + 0x9d => Opcode::SWAP14, + 0x9e => Opcode::SWAP15, + 0x9f => Opcode::SWAP16, + + 0xa0 => Opcode::LOG0, + 0xa1 => Opcode::LOG1, + 0xa2 => Opcode::LOG2, + 0xa3 => Opcode::LOG3, + 0xa4 => Opcode::LOG4, + + 0xf0 => Opcode::CREATE, + 0xf1 => Opcode::CALL, + 0xf2 => Opcode::CALLCODE, + 0xf3 => Opcode::RETURN, + 0xf4 => Opcode::DELEGATECALL, + + 0xff => Opcode::SELFDESTRUCT, + _ => Opcode::INVALID, + } + } +} + +impl Into for Opcode { + fn into(self) -> u8 { + match self { + Opcode::STOP => 0x00, + Opcode::ADD => 0x01, + Opcode::MUL => 0x02, + Opcode::SUB => 0x03, + Opcode::DIV => 0x04, + Opcode::SDIV => 0x05, + Opcode::MOD => 0x06, + Opcode::SMOD => 0x07, + Opcode::ADDMOD => 0x08, + Opcode::MULMOD => 0x09, + Opcode::EXP => 0x0a, + Opcode::SIGNEXTEND => 0x0b, + + Opcode::LT => 0x10, + Opcode::GT => 0x11, + Opcode::SLT => 0x12, + Opcode::SGT => 0x13, + Opcode::EQ => 0x14, + Opcode::ISZERO => 0x15, + Opcode::AND => 0x16, + Opcode::OR => 0x17, + Opcode::XOR => 0x18, + Opcode::NOT => 0x19, + Opcode::BYTE => 0x1a, + + Opcode::SHA3 => 0x20, + + Opcode::ADDRESS => 0x30, + Opcode::BALANCE => 0x31, + Opcode::ORIGIN => 0x32, + Opcode::CALLER => 0x33, + Opcode::CALLVALUE => 0x34, + Opcode::CALLDATALOAD => 0x35, + Opcode::CALLDATASIZE => 0x36, + Opcode::CALLDATACOPY => 0x37, + Opcode::CODESIZE => 0x38, + Opcode::CODECOPY => 0x39, + Opcode::GASPRICE => 0x3a, + Opcode::EXTCODESIZE => 0x3b, + Opcode::EXTCODECOPY => 0x3c, + + Opcode::BLOCKHASH => 0x40, + Opcode::COINBASE => 0x41, + Opcode::TIMESTAMP => 0x42, + Opcode::NUMBER => 0x43, + Opcode::DIFFICULTY => 0x44, + Opcode::GASLIMIT => 0x45, + + Opcode::POP => 0x50, + Opcode::MLOAD => 0x51, + Opcode::MSTORE => 0x52, + Opcode::MSTORE8 => 0x53, + Opcode::SLOAD => 0x54, + Opcode::SSTORE => 0x55, + Opcode::JUMP => 0x56, + Opcode::JUMPI => 0x57, + Opcode::PC => 0x58, + Opcode::MSIZE => 0x59, + Opcode::GAS => 0x5a, + Opcode::JUMPDEST => 0x5b, + + Opcode::PUSH1 => 0x60, + Opcode::PUSH2 => 0x61, + Opcode::PUSH3 => 0x62, + Opcode::PUSH4 => 0x63, + Opcode::PUSH5 => 0x64, + Opcode::PUSH6 => 0x65, + Opcode::PUSH7 => 0x66, + Opcode::PUSH8 => 0x67, + Opcode::PUSH9 => 0x68, + Opcode::PUSH10 => 0x69, + Opcode::PUSH11 => 0x6a, + Opcode::PUSH12 => 0x6b, + Opcode::PUSH13 => 0x6c, + Opcode::PUSH14 => 0x6d, + Opcode::PUSH15 => 0x6e, + Opcode::PUSH16 => 0x6f, + Opcode::PUSH17 => 0x70, + Opcode::PUSH18 => 0x71, + Opcode::PUSH19 => 0x72, + Opcode::PUSH20 => 0x73, + Opcode::PUSH21 => 0x74, + Opcode::PUSH22 => 0x75, + Opcode::PUSH23 => 0x76, + Opcode::PUSH24 => 0x77, + Opcode::PUSH25 => 0x78, + Opcode::PUSH26 => 0x79, + Opcode::PUSH27 => 0x7a, + Opcode::PUSH28 => 0x7b, + Opcode::PUSH29 => 0x7c, + Opcode::PUSH30 => 0x7d, + Opcode::PUSH31 => 0x7e, + Opcode::PUSH32 => 0x7f, + + Opcode::DUP1 => 0x80, + Opcode::DUP2 => 0x81, + Opcode::DUP3 => 0x82, + Opcode::DUP4 => 0x83, + Opcode::DUP5 => 0x84, + Opcode::DUP6 => 0x85, + Opcode::DUP7 => 0x86, + Opcode::DUP8 => 0x87, + Opcode::DUP9 => 0x88, + Opcode::DUP10 => 0x89, + Opcode::DUP11 => 0x8a, + Opcode::DUP12 => 0x8b, + Opcode::DUP13 => 0x8c, + Opcode::DUP14 => 0x8d, + Opcode::DUP15 => 0x8e, + Opcode::DUP16 => 0x8f, + + Opcode::SWAP1 => 0x90, + Opcode::SWAP2 => 0x91, + Opcode::SWAP3 => 0x92, + Opcode::SWAP4 => 0x93, + Opcode::SWAP5 => 0x94, + Opcode::SWAP6 => 0x95, + Opcode::SWAP7 => 0x96, + Opcode::SWAP8 => 0x97, + Opcode::SWAP9 => 0x98, + Opcode::SWAP10 => 0x99, + Opcode::SWAP11 => 0x9a, + Opcode::SWAP12 => 0x9b, + Opcode::SWAP13 => 0x9c, + Opcode::SWAP14 => 0x9d, + Opcode::SWAP15 => 0x9e, + Opcode::SWAP16 => 0x9f, + + Opcode::LOG0 => 0xa0, + Opcode::LOG1 => 0xa1, + Opcode::LOG2 => 0xa2, + Opcode::LOG3 => 0xa3, + Opcode::LOG4 => 0xa4, + + Opcode::CREATE => 0xf0, + Opcode::CALL => 0xf1, + Opcode::CALLCODE => 0xf2, + Opcode::RETURN => 0xf3, + Opcode::DELEGATECALL => 0xf4, + + Opcode::INVALID => 0xfe, + Opcode::SELFDESTRUCT => 0xff, + } + } +} diff --git a/src/vm/opcode/mod.rs b/src/vm/opcode/mod.rs new file mode 100644 index 00000000..f7d4505a --- /dev/null +++ b/src/vm/opcode/mod.rs @@ -0,0 +1,5 @@ +mod code; +mod run; +mod usage; + +pub use self::code::Opcode; diff --git a/src/vm/opcode/run.rs b/src/vm/opcode/run.rs new file mode 100644 index 00000000..8bef0742 --- /dev/null +++ b/src/vm/opcode/run.rs @@ -0,0 +1,73 @@ +use super::Opcode; +use vm::{Machine, Memory, Stack, PC}; + +// TODO: deal with gas limit and other Ethereum-specific things. + +fn push(opcode: Opcode, pc: &mut PC, stack: &mut S) { + let code_u8: u8 = opcode.into(); + let count = code_u8 - 0x5f; + let val = pc.read(count as usize); + stack.push(val); +} + +impl Opcode { + pub fn run(&self, machine: &mut Machine) { + let pc = &mut machine.pc; + let memory = &mut machine.memory; + let stack = &mut machine.stack; + let opcode = self.clone(); + + match opcode { + Opcode::STOP => { + pc.stop(); + }, + + Opcode::ADD => { + let op1 = stack.pop(); + let op2 = stack.pop(); + stack.push(op1 + op2); + }, + + Opcode::MUL => { + let op1 = stack.pop(); + let op2 = stack.pop(); + stack.push(op1 * op2); + }, + + Opcode::SUB => { + let op1 = stack.pop(); + let op2 = stack.pop(); + stack.push(op1 - op2); + }, + + Opcode::DIV => { + let op1 = stack.pop(); + let op2 = stack.pop(); + if op2 == 0.into() { + stack.push(0.into()); + } else { + stack.push(op1 / op2); + } + }, + + // TODO: implement omitted opcodes. + + Opcode::PUSH1 | Opcode::PUSH2 | Opcode::PUSH3 | + Opcode::PUSH4 | Opcode::PUSH5 | Opcode::PUSH6 | + Opcode::PUSH7 | Opcode::PUSH8 | Opcode::PUSH9 | + Opcode::PUSH10 | Opcode::PUSH11 | Opcode::PUSH12 | + Opcode::PUSH13 | Opcode::PUSH14 | Opcode::PUSH15 | + Opcode::PUSH16 | Opcode::PUSH17 | Opcode::PUSH18 | + Opcode::PUSH19 | Opcode::PUSH20 | Opcode::PUSH21 | + Opcode::PUSH22 | Opcode::PUSH23 | Opcode::PUSH24 | + Opcode::PUSH25 | Opcode::PUSH26 | Opcode::PUSH27 | + Opcode::PUSH28 | Opcode::PUSH29 | Opcode::PUSH30 | + Opcode::PUSH31 | Opcode::PUSH32 + => push(opcode, pc, stack), + + _ => { + unimplemented!(); + } + } + } +} diff --git a/src/vm/opcode/usage.rs b/src/vm/opcode/usage.rs new file mode 100644 index 00000000..f79ce25b --- /dev/null +++ b/src/vm/opcode/usage.rs @@ -0,0 +1,123 @@ +use super::Opcode; +use vm::{Machine, Memory, Stack, PC, Gas}; + +const G_ZERO: isize = 0; +const G_BASE: isize = 2; +const G_VERYLOW: isize = 3; +const G_LOW: isize = 5; +const G_MID: isize = 8; +const G_HIGH: isize = 10; +const G_EXTCODE: isize = 700; +const G_BALANCE: isize = 400; +const G_SLOAD: isize = 200; +const G_JUMPDEST: isize = 1; +const G_SSET: isize = 20000; +const G_SRESET: isize = 5000; +const R_SCLEAR: isize = 15000; +const R_SELFDESTRUCT: isize = 24000; +const G_SELFDESTRUCT: isize = 5000; +const G_CREATE: isize = 32000; +const G_CODEDEPOSITE: isize = 200; +const G_CALL: isize = 700; +const G_CALLVALUE: isize = 9000; +const G_CALLSTIPEND: isize = 2300; +const G_NEWACCOUNT: isize = 25000; +const G_EXP: isize = 10; +const G_EXPBYTE: isize = 10; +const G_MEMORY: isize = 3; +const G_TXCREATE: isize = 32000; +const G_TXDATAZERO: isize = 4; +const G_TXDATANONZERO: isize = 68; +const G_TRANSACTION: isize = 21000; +const G_LOG: isize = 375; +const G_LOGDATA: isize = 8; +const G_LOGTOPIC: isize = 375; +const G_SHA3: isize = 30; +const G_SHA3WORD: isize = 6; +const G_COPY: isize = 3; +const G_BLOCKHASH: isize = 20; + +fn memory_cost(a: usize) -> Gas { + let a = a as isize; + (G_MEMORY * a + a * a / 512).into() +} + +// TODO: Implement C_SSTORE, C_CALL and C_SELFDESTRUCT +// TODO: Use a machine_state struct instead of u_ip, u_i and u_s +// TODO: Use machine_state to implement gas cost for EXP, +// CALLDATACOPY, CODECOPY, EXTCODECOPY, LOG0-4, SHA3 + +impl Opcode { + pub fn gas_cost_before(&self, machine: &Machine) -> Gas { + let opcode = self.clone(); + let self_cost: Gas = match opcode { + // Unimplemented + Opcode::SSTORE | Opcode::EXP | Opcode::CALLDATACOPY | + Opcode::CODECOPY | Opcode::EXTCODECOPY | Opcode::LOG0 | + Opcode::LOG1 | Opcode::LOG2 | Opcode::LOG3 | Opcode::LOG4 | + Opcode::CALL | Opcode::CALLCODE | Opcode::DELEGATECALL | + Opcode::SELFDESTRUCT | Opcode::SHA3 | + Opcode::EXTCODESIZE => unimplemented!(), + + Opcode::CREATE => G_CREATE.into(), + Opcode::JUMPDEST => G_JUMPDEST.into(), + Opcode::SLOAD => G_SLOAD.into(), + + // W_zero + Opcode::STOP | Opcode::RETURN => G_ZERO.into(), + + // W_base + Opcode::ADDRESS | Opcode::ORIGIN | Opcode::CALLER | + Opcode::CALLVALUE | Opcode::CALLDATASIZE | Opcode::CODESIZE | + Opcode::GASPRICE | Opcode::COINBASE | Opcode::TIMESTAMP | + Opcode::NUMBER | Opcode::DIFFICULTY | Opcode::GASLIMIT | + Opcode::POP | Opcode::PC | Opcode::MSIZE | + Opcode::GAS => G_BASE.into(), + + // W_verylow + Opcode::ADD | Opcode::SUB | Opcode::NOT | Opcode::LT | + Opcode::GT | Opcode::SLT | Opcode::SGT | Opcode::EQ | + Opcode::ISZERO | Opcode::AND | Opcode::OR | Opcode::XOR | + Opcode::BYTE | Opcode::CALLDATALOAD | Opcode::MLOAD | + Opcode::MSTORE | Opcode::MSTORE8 | Opcode::PUSH1 | + Opcode::PUSH2 | Opcode::PUSH3 | Opcode::PUSH4 | Opcode::PUSH5 | + Opcode::PUSH6 | Opcode::PUSH7 | Opcode::PUSH8 | + Opcode::PUSH9 | Opcode::PUSH10 | Opcode::PUSH11 | + Opcode::PUSH12 | Opcode::PUSH13 | Opcode::PUSH14 | + Opcode::PUSH15 | Opcode::PUSH16 | Opcode::PUSH17 | + Opcode::PUSH18 | Opcode::PUSH19 | Opcode::PUSH20 | + Opcode::PUSH21 | Opcode::PUSH22 | Opcode::PUSH23 | + Opcode::PUSH24 | Opcode::PUSH25 | Opcode::PUSH26 | + Opcode::PUSH27 | Opcode::PUSH28 | Opcode::PUSH29 | + Opcode::PUSH30 | Opcode::PUSH31 | Opcode::PUSH32 | + Opcode::DUP1 | Opcode::DUP2 | Opcode::DUP3 | Opcode::DUP4 | + Opcode::DUP5 | Opcode::DUP6 | Opcode::DUP7 | Opcode::DUP8 | + Opcode::DUP9 | Opcode::DUP10 | Opcode::DUP11 | Opcode::DUP12 | + Opcode::DUP13 | Opcode::DUP14 | Opcode::DUP15 | Opcode::DUP16 | + Opcode::SWAP1 | Opcode::SWAP2 | Opcode::SWAP3 | + Opcode::SWAP4 | Opcode::SWAP5 | Opcode::SWAP6 | Opcode::SWAP7 | + Opcode::SWAP8 | Opcode::SWAP9 | Opcode::SWAP10 | + Opcode::SWAP11 | Opcode::SWAP12 | Opcode::SWAP13 | + Opcode::SWAP14 | Opcode::SWAP15 | Opcode::SWAP16 => G_VERYLOW.into(), + + // W_low + Opcode::MUL | Opcode::DIV | Opcode::SDIV | Opcode::MOD | + Opcode::SMOD | Opcode::SIGNEXTEND => G_LOW.into(), + + // W_mid + Opcode::ADDMOD | Opcode::MULMOD | Opcode::JUMP => G_MID.into(), + + // W_high + Opcode::JUMPI => G_HIGH.into(), + + Opcode::BALANCE => G_BALANCE.into(), + Opcode::BLOCKHASH => G_BLOCKHASH.into(), + Opcode::INVALID => Gas::zero(), + }; + self_cost - memory_cost(machine.memory.active_len()) + } + + pub fn gas_cost_after(&self, machine: &Machine) -> Gas { + memory_cost(machine.memory.active_len()) + } +} diff --git a/src/vm/pc.rs b/src/vm/pc.rs new file mode 100644 index 00000000..da4e97cf --- /dev/null +++ b/src/vm/pc.rs @@ -0,0 +1,51 @@ +use u256::U256; +use std::cmp::{min}; +use super::opcode::Opcode; + +pub struct PC<'a> { + pub position: usize, + code: &'a [u8], + stopped: bool +} + +impl<'a> PC<'a> { + pub fn new(code: &'a [u8]) -> Self { + PC { + position: 0, + code: code, + stopped: false, + } + } + + pub fn peek_opcode(&self) -> Opcode { + let position = self.position; + let opcode: Opcode = self.code[position].into(); + opcode + } + + pub fn read_opcode(&mut self) -> Opcode { + let position = self.position; + let opcode: Opcode = self.code[position].into(); + self.position += 1; + opcode + } + + pub fn stop(&mut self) { + self.stopped = true; + } + + pub fn is_stopped(&self) -> bool { + self.stopped || self.position >= self.code.len() + } + + pub fn read(&mut self, byte_count: usize) -> U256 { + let position = self.position; + self.position += byte_count; + let max = min(position + byte_count, self.code.len()); + U256::from(&self.code[position..max]) + } + + fn len(&self) -> usize { + self.code.len() + } +} diff --git a/src/vm/stack.rs b/src/vm/stack.rs new file mode 100644 index 00000000..c12e1b92 --- /dev/null +++ b/src/vm/stack.rs @@ -0,0 +1,45 @@ +use u256::U256; + +pub trait Stack { + fn new() -> Self; + fn push(&mut self, elem: U256); + fn pop(&mut self) -> U256; + fn peek(&self, no_from_top: usize) -> &U256; + fn has(&self, no_of_elems: usize) -> bool; + fn size(&self) -> usize; +} + +pub struct VectorStack { + stack: Vec, +} + +impl Stack for VectorStack { + fn new() -> VectorStack { + VectorStack { + stack: Vec::new(), + } + } + + fn push(&mut self, elem: U256) { + self.stack.push(elem); + } + + fn pop(&mut self) -> U256 { + match self.stack.pop() { + Some(x) => x, + None => panic!("Empty stack pop.") + } + } + + fn peek(&self, no_from_top: usize) -> &U256 { + &self.stack[self.stack.len() - no_from_top - 1] + } + + fn has(&self, no_of_elems: usize) -> bool { + self.stack.len() >= no_of_elems + } + + fn size(&self) -> usize { + self.stack.len() + } +}