From 9c5358f513957ca26f372a73c4194943e8f25868 Mon Sep 17 00:00:00 2001 From: Jay White Date: Tue, 1 Oct 2024 07:57:49 -0400 Subject: [PATCH 1/3] test: add ForgeScript type This type is intended to allow for running solidity functions from within rust. It is designed primarily to be able to pass rust types as arguments to solidity to enable cross-language testing. --- Cargo.toml | 5 +-- crates/proof-of-sql/Cargo.toml | 5 +-- crates/proof-of-sql/src/tests/mod.rs | 2 + .../proof-of-sql/src/tests/sol_test_util.rs | 45 +++++++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 crates/proof-of-sql/src/tests/sol_test_util.rs diff --git a/Cargo.toml b/Cargo.toml index bcf02605f..2324bda84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,7 @@ license-file = "LICENSE" [workspace.dependencies] ahash = { version = "0.8.11", default-features = false } -# alloy-primitives = { version = "0.8.1" } -# alloy-sol-types = { version = "0.8.1" } +alloy-sol-types = { version = "0.8.5" } ark-bls12-381 = { version = "0.4.0" } ark-curve25519 = { version = "0.4.0" } ark-ec = { version = "0.4.0" } @@ -36,7 +35,6 @@ chrono = { version = "0.4.38", default-features = false } curve25519-dalek = { version = "4", features = ["rand_core"] } derive_more = { version = "0.99" } flexbuffers = { version = "2.0.0" } -# forge-script = { git = "https://github.com/foundry-rs/foundry", tag = "nightly-bf1a39980532f76cd76fd87ee32661180f606435" } indexmap = { version = "2.1", default-features = false } itertools = { version = "0.13.0", default-features = false, features = ["use_alloc"] } lalrpop = { version = "0.21.0" } @@ -56,7 +54,6 @@ serde = { version = "1", default-features = false } serde_json = { version = "1", default-features = false, features = ["alloc"] } snafu = { version = "0.8.4", default-features = false } tiny-keccak = { version = "2.0.2", features = [ "keccak" ] } -# tokio = { version = "1.39.3" } tracing = { version = "0.1.36", default-features = false } tracing-opentelemetry = { version = "0.22.0" } tracing-subscriber = { version = "0.3.0" } diff --git a/crates/proof-of-sql/Cargo.toml b/crates/proof-of-sql/Cargo.toml index eb13d6a6c..f6d1e5804 100644 --- a/crates/proof-of-sql/Cargo.toml +++ b/crates/proof-of-sql/Cargo.toml @@ -50,20 +50,17 @@ tracing = { workspace = true, features = ["attributes"] } zerocopy = { workspace = true } [dev-dependencies] -# alloy-primitives = { workspace = true } -# alloy-sol-types = { workspace = true } +alloy-sol-types = { workspace = true } arrow-csv = { workspace = true } blitzar = { workspace = true } clap = { workspace = true, features = ["derive"] } criterion = { workspace = true, features = ["html_reports"] } -# forge-script = { workspace = true } merlin = { workspace = true } opentelemetry = { workspace = true } opentelemetry-jaeger = { workspace = true } rand = { workspace = true, default-features = false } rand_core = { workspace = true, default-features = false } serde_json = { workspace = true } -# tokio = { workspace = true } tracing = { workspace = true } tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/proof-of-sql/src/tests/mod.rs b/crates/proof-of-sql/src/tests/mod.rs index 13880d03a..888f44b5a 100644 --- a/crates/proof-of-sql/src/tests/mod.rs +++ b/crates/proof-of-sql/src/tests/mod.rs @@ -1 +1,3 @@ // mod sol_test; +mod sol_test_util; +pub(crate) use sol_test_util::{ForgeScript, ForgeScriptError}; diff --git a/crates/proof-of-sql/src/tests/sol_test_util.rs b/crates/proof-of-sql/src/tests/sol_test_util.rs new file mode 100644 index 000000000..9ae41b09d --- /dev/null +++ b/crates/proof-of-sql/src/tests/sol_test_util.rs @@ -0,0 +1,45 @@ +use alloy_sol_types::{private::primitives::Bytes, SolValue}; +use snafu::Snafu; +use std::{ffi::OsStr, io, process::Command}; + +/// Error type returned by [ForgeScript] functions. +#[derive(Debug, Snafu)] +pub enum ForgeScriptError<'a> { + /// The script failed to run. This is usually because `forge` is not installed. + #[snafu(transparent)] + ExecutionFailed { source: io::Error }, + #[snafu(display("Script threw and error. Underlying command: {underlying_command:?}."))] + /// The script threw an error. This could be expected behavior. + SolidityError { underlying_command: &'a Command }, +} + +/// [ForgeScript] enables running solidity from within rust. Ultimately this type calls `forge script`. +/// As a result, `forge` must be installed. +/// See for instructions. +pub struct ForgeScript { + command: Command, +} + +impl ForgeScript { + /// Constructs a new `ForgeScript` for running a solidity function in `path` file, where the function is named `signature`. + pub fn new(path: impl AsRef, signature: impl AsRef) -> Self { + let mut command = Command::new("forge"); + command.arg("script").arg(path).arg("--sig").arg(signature); + Self { command } + } + /// Adds an argument to pass to the script. Only one argument can be passed per use. + pub fn arg(&mut self, arg: impl SolValue) -> &mut Self { + self.command.arg(Bytes::from(arg.abi_encode()).to_string()); + self + } + /// Executes the script as a child process, waiting for it to finish and collecting its status. + pub fn execute(&mut self) -> Result<(), ForgeScriptError> { + self.command + .status()? + .success() + .then_some(()) + .ok_or(ForgeScriptError::SolidityError { + underlying_command: &self.command, + }) + } +} From 77254da7feb08ca3e3e99ecd9a3a24a30662d256 Mon Sep 17 00:00:00 2001 From: Jay White Date: Tue, 1 Oct 2024 07:58:57 -0400 Subject: [PATCH 2/3] ci: install `forge` in ci This allows calls to the `ForgeScript` test type to run within CI. --- .github/workflows/lint-and-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 80a30d729..ae2699609 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -88,6 +88,10 @@ jobs: sudo apt-get install -y clang lld - name: Run cargo test run: cargo test --all-features + - name: Install Foundry/forge for solidity tests + uses: foundry-rs/foundry-toolchain@v1 + - name: Run solidity tests (ignored by default) + run: cargo test --all-features --package proof-of-sql --lib -- tests::sol_test --show-output --ignored - name: Run cargo test without rayon run: cargo test --no-default-features --features="arrow blitzar" - name: Dry run cargo test (proof-of-sql) (test feature only) From a5c5a6769b71127404d565b3fcf742cfc7125eab Mon Sep 17 00:00:00 2001 From: Jay White Date: Tue, 1 Oct 2024 08:33:02 -0400 Subject: [PATCH 3/3] test: reenable and refactor solidity tests This refactors the solidity tests to use the `ForgeScript` type rather than the unstable `forge_script` crate. --- crates/proof-of-sql/src/tests/mod.rs | 2 +- crates/proof-of-sql/src/tests/sol_test.rs | 61 +++++++++++------------ 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/crates/proof-of-sql/src/tests/mod.rs b/crates/proof-of-sql/src/tests/mod.rs index 888f44b5a..595f2d4a0 100644 --- a/crates/proof-of-sql/src/tests/mod.rs +++ b/crates/proof-of-sql/src/tests/mod.rs @@ -1,3 +1,3 @@ -// mod sol_test; +mod sol_test; mod sol_test_util; pub(crate) use sol_test_util::{ForgeScript, ForgeScriptError}; diff --git a/crates/proof-of-sql/src/tests/sol_test.rs b/crates/proof-of-sql/src/tests/sol_test.rs index 312d971f0..1e00ffe2f 100644 --- a/crates/proof-of-sql/src/tests/sol_test.rs +++ b/crates/proof-of-sql/src/tests/sol_test.rs @@ -1,42 +1,39 @@ -use alloy_primitives::{Bytes, U256}; -use alloy_sol_types::{sol, SolValue}; -use forge_script::ScriptArgs; +use crate::tests::{ForgeScript, ForgeScriptError}; +use alloy_sol_types::{private::primitives::U256, sol}; -#[tokio::test(flavor = "multi_thread")] -async fn we_can_run_solidity_script_from_rust() { - ScriptArgs { - path: "./sol_src/tests/TestScript.t.sol".to_string(), - sig: "rustTestWeCanThrowErrorDependingOnParameter".to_string(), - args: vec![U256::from(1234).to_string()], - ..Default::default() - } - .run_script() - .await +#[test] +#[ignore = "Because forge needs to be installed, we are ignoring this test by default. They will still be run from within the ci."] +fn we_can_run_solidity_script_from_rust() { + ForgeScript::new( + "./sol_src/tests/TestScript.t.sol", + "rustTestWeCanThrowErrorDependingOnParameter", + ) + .arg(U256::from(1234)) + .execute() .unwrap(); - assert!(ScriptArgs { - path: "./sol_src/tests/TestScript.t.sol".to_string(), - sig: "rustTestWeCanThrowErrorDependingOnParameter".to_string(), - args: vec![U256::from(0).to_string()], - ..Default::default() - } - .run_script() - .await - .is_err()); + assert!(matches!( + ForgeScript::new( + "./sol_src/tests/TestScript.t.sol", + "rustTestWeCanThrowErrorDependingOnParameter", + ) + .arg(U256::from(0)) + .execute(), + Err(ForgeScriptError::SolidityError { .. }) + )); } -#[tokio::test(flavor = "multi_thread")] -async fn we_can_pass_custom_struct_into_solidity_from_rust() { +#[test] +#[ignore = "Because forge needs to be installed, we are ignoring this test by default. They will still be run from within the ci."] +fn we_can_pass_custom_struct_into_solidity_from_rust() { sol!("./sol_src/tests/TestScript.t.sol"); let arg = TestScript::CustomStruct { value: U256::from(1234), }; - ScriptArgs { - path: "./sol_src/tests/TestScript.t.sol".to_string(), - sig: "rustTestWeCanAcceptCustomStructAsEncodedBytes".to_string(), - args: vec![Bytes::from(arg.abi_encode()).to_string()], - ..Default::default() - } - .run_script() - .await + ForgeScript::new( + "./sol_src/tests/TestScript.t.sol", + "rustTestWeCanAcceptCustomStructAsEncodedBytes", + ) + .arg(arg) + .execute() .unwrap(); }