Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abi specs #688

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/expensive.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ jobs:
uses: Swatinem/rust-cache@v1

- name: Build
run: cargo test --workspace --all-features --no-run --locked -- --ignored
run: cargo test --workspace --features solc-backend --no-run --locked -- --ignored
- name: Run expensive tests
id: expensive_tests
run: cargo test --workspace --all-features --verbose -- --ignored
run: cargo test --workspace --features solc-backend --verbose -- --ignored
- name: Report
if: failure() && steps.expensive_tests.outcome == 'failure'
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ jobs:
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1
- name: Build
run: cargo test --workspace --all-features --no-run --locked
run: cargo test --workspace --features solc-backend --no-run --locked
- name: Run tests
run: cargo test --workspace --all-features --verbose
run: cargo test --workspace --features solc-backend --verbose

wasm-test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -179,7 +179,7 @@ jobs:
toolchain: stable
override: true
- name: Build
run: cargo build --all-features --release && strip target/release/fe && mv target/release/fe target/release/${{ matrix.BIN_FILE }}
run: cargo build --features solc-backend --release && strip target/release/fe && mv target/release/fe target/release/${{ matrix.BIN_FILE }}
- name: Release
uses: softprops/action-gh-release@v1
with:
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ docker-wasm-test:

.PHONY: coverage
coverage:
cargo tarpaulin --workspace --all-features --verbose --timeout 120 --exclude-files 'tests/*' --exclude-files 'main.rs' --out xml html -- --skip differential::
cargo tarpaulin --workspace --features solc-backend --verbose --timeout 120 --exclude-files 'tests/*' --exclude-files 'main.rs' --out xml html -- --skip differential::

.PHONY: clippy
clippy:
cargo clippy --workspace --all-targets --all-features -- -D warnings -A clippy::upper-case-acronyms -A clippy::large-enum-variant -W clippy::redundant_closure_for_method_calls
cargo clippy --workspace --all-targets --features solc-backend -- -D warnings -A clippy::upper-case-acronyms -A clippy::large-enum-variant -W clippy::redundant_closure_for_method_calls

.PHONY: rustfmt
rustfmt:
Expand Down
48 changes: 48 additions & 0 deletions crates/driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct CompiledContract {
pub yul: String,
#[cfg(feature = "solc-backend")]
pub bytecode: String,
#[cfg(feature = "solc-backend")]
pub runtime_bytecode: String,
}

#[derive(Debug)]
Expand Down Expand Up @@ -128,6 +130,15 @@ fn compile_module_id(
IndexMap::new()
};

// compile runtimes to yul
let yul_contract_runtimes = fe_yulgen::compile_runtimes(db, lowered_module_id);

let _bytecode_contract_runtimes = if _with_bytecode {
compile_yul_runtimes(yul_contract_runtimes.iter(), _optimize)
} else {
IndexMap::new()
};

// combine all of the named contract maps
let contracts = json_abis
.keys()
Expand All @@ -143,6 +154,12 @@ fn compile_module_id(
} else {
"".to_string()
},
#[cfg(feature = "solc-backend")]
runtime_bytecode: if _with_bytecode {
_bytecode_contract_runtimes[name].to_owned()
} else {
"".to_string()
},
},
)
})
Expand Down Expand Up @@ -185,3 +202,34 @@ fn compile_yul(
#[cfg(not(feature = "solc-backend"))]
IndexMap::new()
}

fn compile_yul_runtimes(
_contracts: impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
_optimize: bool,
) -> IndexMap<String, String> {
#[cfg(feature = "solc-backend")]
{
match fe_yulc::compile_runtimes(_contracts, _optimize) {
Err(error) => {
for error in serde_json::from_str::<Value>(&error.0)
.expect("unable to deserialize json output")["errors"]
.as_array()
.expect("errors not an array")
{
eprintln!(
"Error: {}",
error["formattedMessage"]
.as_str()
.expect("error value not a string")
.replace("\\\n", "\n")
)
}
panic!("Yul compilation failed with the above errors")
}
Ok(contracts) => contracts,
}
}

#[cfg(not(feature = "solc-backend"))]
IndexMap::new()
}
20 changes: 19 additions & 1 deletion crates/fe/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ arg_enum! {
Ast,
LoweredAst,
Bytecode,
RuntimeBytecode,
Tokens,
Yul,
}
Expand Down Expand Up @@ -52,7 +53,15 @@ pub fn main() {
.short("e")
.long("emit")
.help("Comma separated compile targets e.g. -e=bytecode,yul")
.possible_values(&["abi", "bytecode", "ast", "tokens", "yul", "loweredAst"])
.possible_values(&[
"abi",
"bytecode",
"runtimeBytecode",
"ast",
"tokens",
"yul",
"loweredAst",
])
.default_value("abi,bytecode")
.use_delimiter(true)
.takes_value(true),
Expand Down Expand Up @@ -240,6 +249,15 @@ fn write_compiled_module(
let file_name = format!("{}.bin", &name);
write_output(&contract_output_dir.join(file_name), &contract.bytecode)?;
}

#[cfg(feature = "solc-backend")]
if targets.contains(&CompilationTarget::RuntimeBytecode) {
let file_name = format!("{}.runtime.bin", &name);
write_output(
&contract_output_dir.join(file_name),
&contract.runtime_bytecode,
)?;
}
}

Ok(())
Expand Down
20 changes: 20 additions & 0 deletions crates/fv/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
authors = ["The Fe Developers <[email protected]>"]
categories = ["cryptography::cryptocurrencies", "command-line-utilities", "development-tools"]
description = "Fe formal verification utilities"
edition = "2021"
keywords = ["ethereum", "fe", "yul", "smart", "contract", "compiler"]
license = "GPL-3.0-or-later"
name = "fv"
readme = "README.md"
repository = "https://github.com/ethereum/fe"
version = "0.15.0-alpha"

[features]
solc-backend = ["fe-driver/solc-backend"]
kevm-backend = []

[dependencies]
fe-driver = {path = "../driver", version = "^0.15.0-alpha"}
fe-yulgen = {path = "../yulgen", version = "^0.15.0-alpha"}
fe-test-files = {path = "../test-files", version = "^0.15.0-alpha"}
41 changes: 41 additions & 0 deletions crates/fv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Fe Fv

This crate contains utilities for building and running kevm specs.

## kevm setup

A system installation of [evm-semantics](https://github.com/fe-lang/evm-semantics) is required to run the specs. Please clone the repository and follow the installation directions.

While in the `evm-semantics` project, `kompile` the Fe verification module:

```commandline
$ export PATH=$PATH:/path/to/evm-semantics/.build/usr/bin
$ kevm kompile --backend haskell tests/specs/fe/verification.k \
--directory tests/specs/fe/verification/haskell \
--main-module VERIFICATION \
--syntax-module VERIFICATION \
--concrete-rules-file tests/specs/fe/concrete-rules.txt \
-I /usr/lib/kevm/include/kframework -I /usr/lib/kevm/blockchain-k-plugin/include/kframework
```

## running the proofs

Once the evm-semantics project has been built, set the `KEVM_PATH` environment variable:

```commandline
$ export KEVM_PATH=/path/to/evm-semantics
```

and then run the tests:

```commandline
$ cargo test --features "solc-backend, kevm-backend"
```

*Note: If you are working on a resource constrained device, you may not be able to run all tests simultaneously. If issues are encountered, run tests in smaller groups.*

e.g.

```commandline
$ cargo test sanity_returns_42 --features "solc-backend, kevm-backend"
```
63 changes: 63 additions & 0 deletions crates/fv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#![cfg(feature = "kevm-backend")]
use fe_yulgen::Db;
use std::path::Path;
use std::process::Command;
use std::{env, fs};

const SPECS_DIR: &str = "tests/specs/fe/";

pub fn kevm_path() -> String {
env::var("KEVM_PATH").expect("`KEVM_PATH` not set")
}

pub fn run_spec(name: &str, src_path: &str, src: &str, spec: &str) -> Result<(), String> {
let kevm_path = kevm_path();

let spec = build_spec(name, src_path, src, spec);
let spec_path = Path::new(&kevm_path)
.join(SPECS_DIR)
.join(name)
.with_extension("k");
fs::write(spec_path.clone(), spec).expect("unable to write file");

let path = env::var("PATH").expect("PATH is not set");

let out = Command::new("kevm")
.arg("prove")
.arg(spec_path.to_str().unwrap())
.arg("--backend")
.arg("haskell")
.arg("--format-failures")
.arg("--directory")
.arg("tests/specs/fe/verification/haskell")
.env("PATH", format!(".build/usr/bin:{}", path))
.current_dir(&kevm_path)
.output()
.expect("failed to execute process");

if out.status.code() != Some(0) {
Err(format!(
"{}\n{}",
String::from_utf8_lossy(&out.stderr),
String::from_utf8_lossy(&out.stdout)
))
} else {
Ok(())
}
}

pub fn build_spec(name: &str, src_path: &str, src: &str, spec: &str) -> String {
let mut db = Db::default();
let module = fe_driver::compile_single_file(&mut db, src_path, src, true, true).unwrap();

// replace placeholders
let mut new_spec = spec.to_owned().replace("$TEST_NAME", &name.to_uppercase());
for (name, contract) in module.contracts.iter() {
new_spec = new_spec.replace(
&format!("${}::RUNTIME", name),
&format!("\"0x{}\"", contract.runtime_bytecode),
)
}

new_spec
}
68 changes: 68 additions & 0 deletions crates/fv/tests/specs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#![cfg(feature = "kevm-backend")]

/// Checks if a contract spec is valid.
macro_rules! test_spec {
($name:ident, $src_path:expr, $spec_path:expr) => {
#[test]
fn $name() {
let src = fe_test_files::fixture(concat!("kspecs/", $src_path));
let spec = fe_test_files::fixture(concat!("kspecs/", $spec_path));

if let Err(output) = fv::run_spec(
&stringify!($name).replace("_", "-"),
$src_path,
&src,
&spec
) {
panic!("{}", output)
}
}
};
}

/// Checks if a contract spec is invalid.
macro_rules! test_spec_invalid {
($name:ident, $src_path:expr, $spec_path:expr) => {
#[test]
fn $name() {
let src = fe_test_files::fixture(concat!("kspecs/", $src_path));
let spec = fe_test_files::fixture(concat!("kspecs/", $spec_path));

match fv::run_spec(&stringify!($name).replace("_", "-"), $src_path, &src, &spec) {
Ok(()) => panic!("spec is valid"),
Err(output) => {
// we want to make sure it didn't fail for some other reason
if !output.contains("the claimed implication is not valid") {
panic!("spec claim was not checked {}", output)
}
}
}
}
};
}

test_spec! { sanity_returns_42, "sanity/foo.fe", "sanity/returns_42.k" }
test_spec! { sanity_returns_in, "sanity/foo.fe", "sanity/returns_in.k" }
test_spec! { sanity_returns_in_cond1, "sanity/foo.fe", "sanity/returns_in_cond1.k" }
test_spec! { sanity_returns_in_cond2, "sanity/foo.fe", "sanity/returns_in_cond2.k" }

// these are just for extra sanity
test_spec_invalid! { sanity_returns_42_invalid, "sanity/foo.fe", "sanity/returns_42_invalid.k" }
test_spec_invalid! { sanity_returns_in_invalid, "sanity/foo.fe", "sanity/returns_in_invalid.k" }
test_spec_invalid! { sanity_returns_in_cond2_invalid, "sanity/foo.fe", "sanity/returns_in_cond2_invalid.k" }

// encode/decode success
test_spec! { abi_u256, "abi/foo.fe", "abi/u256.k" }
test_spec! { abi_u8, "abi/foo.fe", "abi/u8.k" }
test_spec! { abi_address, "abi/foo.fe", "abi/address.k" }
test_spec! { abi_address_u16, "abi/foo.fe", "abi/address_u16.k" }

// decode revert
test_spec! { abi_address_revert, "abi/foo.fe", "abi/address_revert.k" }
test_spec! { abi_address_u16_revert_0, "abi/foo.fe", "abi/address_u16_revert_0.k" }
test_spec! { abi_address_u16_revert_1, "abi/foo.fe", "abi/address_u16_revert_1.k" }

// storage
test_spec! { store_u256, "storage/foo.fe", "storage/store_u256.k" }
test_spec! { store_sol_map, "storage/foo.fe", "storage/store_sol_map.k" }
test_spec! { store_fe_map, "storage/foo.fe", "storage/store_fe_map.k" }
Loading