From edd122b0624f679d50daa72584efba1be772fa2f Mon Sep 17 00:00:00 2001 From: Nirvan Tyagi Date: Mon, 21 Feb 2022 15:36:14 -0800 Subject: [PATCH] Marlin contract verifier key and proof parsing --- Cargo.toml | 1 + zokrates_core/Cargo.toml | 3 + zokrates_core/src/proof_system/ark/marlin.rs | 6 +- .../src/proof_system/scheme/marlin.rs | 288 +++++++++++++++++- zokrates_solidity_test/Cargo.toml | 19 ++ zokrates_solidity_test/README.md | 7 + .../contracts/simple_storage.sol | 18 ++ zokrates_solidity_test/src/address.rs | 34 +++ zokrates_solidity_test/src/contract.rs | 147 +++++++++ zokrates_solidity_test/src/evm.rs | 121 ++++++++ zokrates_solidity_test/src/lib.rs | 82 +++++ 11 files changed, 720 insertions(+), 6 deletions(-) create mode 100644 zokrates_solidity_test/Cargo.toml create mode 100644 zokrates_solidity_test/README.md create mode 100644 zokrates_solidity_test/contracts/simple_storage.sol create mode 100644 zokrates_solidity_test/src/address.rs create mode 100644 zokrates_solidity_test/src/contract.rs create mode 100644 zokrates_solidity_test/src/evm.rs create mode 100644 zokrates_solidity_test/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 50d4c4ce..00ac2ca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "zokrates_abi", "zokrates_test", "zokrates_core_test", + "zokrates_solidity_test", ] exclude = ["zokrates_js"] diff --git a/zokrates_core/Cargo.toml b/zokrates_core/Cargo.toml index 0618a930..a6ee2018 100644 --- a/zokrates_core/Cargo.toml +++ b/zokrates_core/Cargo.toml @@ -63,7 +63,10 @@ sha2 = { version = "0.9.3", optional = true } [dev-dependencies] wasm-bindgen-test = "^0.3.0" pretty_assertions = "0.6.1" +ethabi = "16.0.0" +primitive-types = { version = "0.10", features = ["rlp"] } zokrates_fs_resolver = { version = "0.5", path = "../zokrates_fs_resolver"} +zokrates_solidity_test = { path = "../zokrates_solidity_test"} [build-dependencies] cc = { version = "1.0", features = ["parallel"], optional = true } diff --git a/zokrates_core/src/proof_system/ark/marlin.rs b/zokrates_core/src/proof_system/ark/marlin.rs index f5102ee9..55286e05 100644 --- a/zokrates_core/src/proof_system/ark/marlin.rs +++ b/zokrates_core/src/proof_system/ark/marlin.rs @@ -16,6 +16,7 @@ use ark_poly_commit::{ }; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use sha2::Sha256; +use rand_0_8::SeedableRng; use zokrates_field::{ArkFieldExtensions, Field}; @@ -31,7 +32,6 @@ const MINIMUM_CONSTRAINT_COUNT: usize = 2; impl UniversalBackend for Ark { fn universal_setup(size: u32) -> Vec { - use rand_0_8::SeedableRng; let rng = &mut rand_0_8::rngs::StdRng::from_entropy(); @@ -131,8 +131,6 @@ impl Backend for Ark { ) -> Proof<>::ProofPoints> { let computation = Computation::with_witness(program, witness); - use rand_0_8::SeedableRng; - let rng = &mut rand_0_8::rngs::StdRng::from_entropy(); let pk = IndexProverKey::< @@ -299,8 +297,6 @@ impl Backend for Ark { }, }; - use rand_0_8::SeedableRng; - let rng = &mut rand_0_8::rngs::StdRng::from_entropy(); ArkMarlin::< diff --git a/zokrates_core/src/proof_system/scheme/marlin.rs b/zokrates_core/src/proof_system/scheme/marlin.rs index 5f59756c..cf1b0389 100644 --- a/zokrates_core/src/proof_system/scheme/marlin.rs +++ b/zokrates_core/src/proof_system/scheme/marlin.rs @@ -1,5 +1,6 @@ use crate::proof_system::scheme::{Scheme, UniversalScheme}; -use crate::proof_system::{G1Affine, G2Affine}; +use crate::proof_system::{G1Affine, G2Affine, NotBw6_761Field}; +use crate::proof_system::solidity::{SolidityCompatibleScheme, SolidityCompatibleField, solidity_pairing_lib}; use serde::{Deserialize, Serialize}; use zokrates_field::Field; @@ -49,3 +50,288 @@ impl Scheme for Marlin { } impl UniversalScheme for Marlin {} + +impl SolidityCompatibleScheme for Marlin { + fn export_solidity_verifier(vk: >::VerificationKey) -> String { + let (template, solidity_pairing_lib) = + (String::from(CONTRACT_TEMPLATE), solidity_pairing_lib(false)); + + // Replace public parameters in template + let src = template + .replace("<%vk_num_variables%>", &vk.num_variables.to_string()) + .replace("<%vk_num_constraints%>", &vk.num_constraints.to_string()) + .replace("<%vk_num_non_zero%>", &vk.num_non_zero.to_string()) + .replace("<%vk_num_instance_variables%>", &vk.num_instance_variables.to_string()) + .replace("<%vk_index_comms_length%>", &vk.index_comms.len().to_string()) + .replace("<%vk_populate_index_comms%>", &{ + let mut populate_index_comms = String::new(); + for (i, (g, _)) in vk.index_comms.iter().enumerate() { + populate_index_comms.push_str(&format!("vk.index_comms[{}] = Pairing.G1Point({});", i, &g.to_string())); + if i < vk.index_comms.len() - 1 { + populate_index_comms.push_str("\n "); + } + } + populate_index_comms + }) + .replace("<%vk_kzg_g%>", &vk.vk.g.to_string()) + .replace("<%vk_kzg_gamma_g%>", &vk.vk.gamma_g.to_string()) + .replace("<%vk_kzg_h%>", &vk.vk.h.to_string()) + .replace("<%vk_kzg_beta_h%>", &vk.vk.beta_h.to_string()) + .replace("<%vk_max_degree%>", &vk.max_degree.to_string()) + .replace("<%vk_supported_degree%>", &vk.supported_degree.to_string()) + .replace("<%vk_degree_bounds_length%>", &vk.degree_bounds_and_shift_powers.as_ref().unwrap().len().to_string()) + .replace("<%vk_populate_degree_bounds%>", &{ + let mut populate_degree_bounds = String::new(); + for (i, (b, g)) in vk.degree_bounds_and_shift_powers.as_ref().unwrap().iter().enumerate() { + populate_degree_bounds.push_str(&format!("vk.degree_bounds[{}] = {};", i, &b.to_string())); + populate_degree_bounds.push_str("\n "); + populate_degree_bounds.push_str(&format!("vk.degree_shifted_powers[{}] = Pairing.G1Point({});", i, &g.to_string())); + if i < vk.degree_bounds_and_shift_powers.as_ref().unwrap().len() - 1 { + populate_degree_bounds.push_str("\n "); + } + } + populate_degree_bounds + }); + + + format!( + "{}{}", + solidity_pairing_lib, src + ) + } +} + +const CONTRACT_TEMPLATE: &str = r#" +contract Verifier { + using Pairing for *; + struct KZGVerifierKey { + Pairing.G1Point g; + Pairing.G1Point gamma_g; + Pairing.G2Point h; + Pairing.G2Point beta_h; + } + struct VerifierKey { + // index_info + uint64 num_variables; + uint64 num_constraints; + uint64 num_non_zero; + uint64 num_instance_variables; + // index commitments + Pairing.G1Point[] index_comms; + // verifier key + KZGVerifierKey vk; + uint64 max_degree; + uint64 supported_degree; + uint64[] degree_bounds; + Pairing.G1Point[] degree_shifted_powers; + } + struct Proof { + Pairing.G1Point[] comms_1; + Pairing.G1Point[] comms_2; + Pairing.G1Point degree_bound_comms_2_g1; + Pairing.G1Point[] comms_3; + Pairing.G1Point degree_bound_comms_3_g2; + uint256[] evals; + Pairing.G1Point batch_lc_proof_1; + uint256 degree_bound_batch_lc_proof_1; + Pairing.G1Point batch_lc_proof_2; + } + function verifierKey() internal pure returns (VerifierKey memory vk) { + vk.num_variables = <%vk_num_variables%>; + vk.num_constraints = <%vk_num_constraints%>; + vk.num_non_zero = <%vk_num_non_zero%>; + vk.num_instance_variables = <%vk_num_instance_variables%>; + vk.index_comms = new Pairing.G1Point[](<%vk_index_comms_length%>); + <%vk_populate_index_comms%> + vk.vk.g = Pairing.G1Point(<%vk_kzg_g%>); + vk.vk.gamma_g = Pairing.G1Point(<%vk_kzg_gamma_g%>); + vk.vk.h = Pairing.G2Point(<%vk_kzg_h%>); + vk.vk.beta_h = Pairing.G2Point(<%vk_kzg_beta_h%>); + vk.max_degree = <%vk_max_degree%>; + vk.supported_degree = <%vk_supported_degree%>; + vk.degree_bounds = new uint64[](<%vk_degree_bounds_length%>); + vk.degree_shifted_powers = new Pairing.G1Point[](<%vk_degree_bounds_length%>); + <%vk_populate_degree_bounds%> + } + function verify(uint[] memory input, Proof memory proof) public view returns (uint) { + uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + VerifierKey memory vk = verifierKey(); + for (uint i = 0; i < input.length; i++) { + require(input[i] < snark_scalar_field); + } + return 0; + } +} +"#; + +#[cfg(test)] +mod tests { + use crate::flat_absy::{FlatParameter, FlatVariable}; + use crate::ir::{Interpreter, Prog, QuadComb, Statement}; + use crate::proof_system::{UniversalBackend, Backend, Proof, Fr}; + use crate::proof_system::ark::{Ark, parse_fr}; + use zokrates_field::ArkFieldExtensions; + + use super::*; + use zokrates_field::{Bn128Field}; + use zokrates_solidity_test::{ + contract::Contract, + evm::Evm, + address::Address, + to_be_bytes, + }; + use rand_0_8::{rngs::StdRng, SeedableRng}; + use ethabi::{Token}; + use primitive_types::U256; + + + /// Helper methods for parsing group structure + pub fn encode_g1_element(g: &G1Affine) -> Token { + Token::Tuple(vec![ + Token::Uint(U256::from(&hex::decode(&g.0.trim_start_matches("0x")).unwrap()[..])), + Token::Uint(U256::from(&hex::decode(&g.1.trim_start_matches("0x")).unwrap()[..])), + ]) + } + + pub fn encode_g2_element(g: &G2Affine) -> Token { + + Token::Tuple(vec![ + Token::FixedArray(vec![ + Token::Uint(U256::from(&hex::decode(&g.0.0.trim_start_matches("0x")).unwrap()[..])), + Token::Uint(U256::from(&hex::decode(&g.0.1.trim_start_matches("0x")).unwrap()[..])), + ]), + Token::FixedArray(vec![ + Token::Uint(U256::from(&hex::decode(&g.1.0.trim_start_matches("0x")).unwrap()[..])), + Token::Uint(U256::from(&hex::decode(&g.1.1.trim_start_matches("0x")).unwrap()[..])), + ]), + ]) + } + + pub fn encode_fr_element(f: &Fr) -> Token { + Token::Uint(U256::from(&hex::decode(&f.trim_start_matches("0x")).unwrap()[..])) + } + + fn encode_verify_input(proof: Proof<>::ProofPoints>,) -> Vec { + let input = Token::Array(proof.inputs.iter().map(|s| { + let bytes = hex::decode(s.trim_start_matches("0x")).unwrap(); + debug_assert_eq!(bytes.len(), 32); + Token::Uint(U256::from(&bytes[..])) + }).collect::>()); + + let comms_1_token = Token::Array(proof.proof.commitments[0].iter().map(|(c, _)|{ + encode_g1_element(c) + }).collect::>()); + + let comms_2_token = Token::Array(proof.proof.commitments[1].iter().map(|(c, _)|{ + encode_g1_element(c) + }).collect::>()); + + let degree_bound_comms_2_g1_token = encode_g1_element(proof.proof.commitments[1][1].1.as_ref().unwrap()); + + let comms_3_token = Token::Array(proof.proof.commitments[2].iter().map(|(c, _)|{ + encode_g1_element(c) + }).collect::>()); + + let degree_bound_comms_3_g2_token = encode_g1_element(proof.proof.commitments[2][0].1.as_ref().unwrap()); + + let evals_token = Token::Array(proof.proof.evaluations.into_iter().map(|f| { + encode_fr_element(&parse_fr::(&Bn128Field::into_ark(f))) + }).collect::>()); + + let pc_lc_opening_1_token = encode_g1_element(&proof.proof.pc_proof_proof[0].0); + let degree_bound_pc_lc_opening_1_token = encode_fr_element(&parse_fr::(&Bn128Field::into_ark(proof.proof.pc_proof_proof[0].1.clone().unwrap()))); + let pc_lc_opening_2_token = encode_g1_element(&proof.proof.pc_proof_proof[1].0); + + let proof_tokens = vec![ + comms_1_token, + comms_2_token, + degree_bound_comms_2_g1_token, + comms_3_token, + degree_bound_comms_3_g2_token, + evals_token, + pc_lc_opening_1_token, + degree_bound_pc_lc_opening_1_token, + pc_lc_opening_2_token, + ]; + + vec![input, Token::Tuple(proof_tokens)] + } + + #[test] + fn verify_solidity_bn128() { + let program: Prog = Prog { + arguments: vec![FlatParameter::private(FlatVariable::new(0))], + return_count: 1, + statements: vec![ + Statement::constraint( + QuadComb::from_linear_combinations( + FlatVariable::new(0).into(), + FlatVariable::new(0).into(), + ), + FlatVariable::new(1), + ), + Statement::constraint(FlatVariable::new(1), FlatVariable::public(0)), + ], + }; + + let srs = >::universal_setup(5); + let keypair = + >::setup(srs, program.clone().into()) + .unwrap(); + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(42)]) + .unwrap(); + + let proof = >::generate_proof( + program.clone(), + witness, + keypair.pk, + ); + + //let ans = >::verify(keypair.vk, proof); + //assert!(ans); + + let mut src = >::export_solidity_verifier(keypair.vk); + src = src.replace("\"", "\\\""); + + let solc_config = r#" + { + "language": "Solidity", + "sources": { + "input.sol": { "content": "<%src%>" } + }, + "settings": { + "optimizer": { "enabled": <%opt%> }, + "outputSelection": { + "*": { + "*": [ + "evm.bytecode.object", "abi" + ], + "": [ "*" ] } } + } + }"# + .replace("<%opt%>", &true.to_string()) + .replace("<%src%>", &src); + + let contract = Contract::compile_from_config(&solc_config, "Verifier").unwrap(); + + // Setup EVM + let mut rng = StdRng::seed_from_u64(0u64); + let mut evm = Evm::new(); + let deployer = Address::random(&mut rng); + evm.create_account(&deployer, 0); + + // Deploy contract + let create_result = evm.deploy(contract.encode_create_contract_bytes(&[]).unwrap(), &deployer).unwrap(); + let contract_addr = create_result.addr.clone(); + println!("Contract deploy gas cost: {}", create_result.gas); + + // Call verify function on contract + let result = evm.call(contract.encode_call_contract_bytes("verify", &encode_verify_input(proof)).unwrap(), &contract_addr, &deployer).unwrap(); + //assert_eq!(&result.out, &to_be_bytes(&U256::from(1))); + println!("{:?}", result); + + } +} diff --git a/zokrates_solidity_test/Cargo.toml b/zokrates_solidity_test/Cargo.toml new file mode 100644 index 00000000..28acfe89 --- /dev/null +++ b/zokrates_solidity_test/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "zokrates_solidity_test" +version = "0.1.0" +authors = ["Nirvan Tyagi "] +edition = "2018" + +# Modeled after the testing pipeline of the Fe project: https://github.com/ethereum/fe/ +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ethabi = "16.0.0" +primitive-types = { version = "0.10", features = ["rlp"] } +hex = { version = "0.4" } +bytes = { version = "1.1", default-features = false } +serde_json = { version = "1.0" } +rand = { version = "0.8" } + +revm = { git = "https://github.com/bluealloy/revm", version = "1.2" } +solc = { git = "https://github.com/g-r-a-n-t/solc-rust", rev = "52d4146" } diff --git a/zokrates_solidity_test/README.md b/zokrates_solidity_test/README.md new file mode 100644 index 00000000..9c793559 --- /dev/null +++ b/zokrates_solidity_test/README.md @@ -0,0 +1,7 @@ +# Installation +```bash +sudo apt install cmake libboost-all-dev +sudo add-apt-repository ppa:ethereum/ethereum +sudo apt-get update +sudo apt-get install solc +``` \ No newline at end of file diff --git a/zokrates_solidity_test/contracts/simple_storage.sol b/zokrates_solidity_test/contracts/simple_storage.sol new file mode 100644 index 00000000..7b2424aa --- /dev/null +++ b/zokrates_solidity_test/contracts/simple_storage.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +contract SimpleStorage { + Var myVariable; + + struct Var { + uint v; + } + + function set(Var memory x) public { + myVariable = x; + } + + function get() public view returns (uint) { + return myVariable.v; + } +} \ No newline at end of file diff --git a/zokrates_solidity_test/src/address.rs b/zokrates_solidity_test/src/address.rs new file mode 100644 index 00000000..0ff7d3ee --- /dev/null +++ b/zokrates_solidity_test/src/address.rs @@ -0,0 +1,34 @@ +use ethabi::token::Token; +use primitive_types::H160; +use rand::Rng; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Address(pub H160); + +impl Address { + pub fn random(rng: &mut R) -> Self { + Self(H160(rng.gen())) + } + + pub fn as_token(&self) -> Token { + Token::Address(self.0) + } +} + +impl AsRef for Address { + fn as_ref(&self) -> &H160 { + &self.0 + } +} + +impl From for Address { + fn from(hash: H160) -> Self { + Self(hash) + } +} + +impl Into for Address { + fn into(self) -> Token { + self.as_token() + } +} diff --git a/zokrates_solidity_test/src/contract.rs b/zokrates_solidity_test/src/contract.rs new file mode 100644 index 00000000..a20ac039 --- /dev/null +++ b/zokrates_solidity_test/src/contract.rs @@ -0,0 +1,147 @@ +use ethabi::{Contract as ContractAbi, Token}; +use serde_json::from_str; +use solc::compile; + +use std::{fs::File, io::Read, path::Path}; + +use crate::{Error, EvmTestError}; + +#[derive(Clone)] +pub struct Contract { + pub binary: Vec, + pub abi: ContractAbi, +} + +impl Contract { + pub fn new(binary: Vec, abi: ContractAbi) -> Self { + Self { binary, abi } + } + + pub fn compile_from_solidity_file>( + path: P, + contract_name: &str, + opt: bool, + ) -> Result { + // Load source file + let mut src_file = File::open(path) + .map_err(|_| Box::new(EvmTestError("src file open failed".to_string())))?; + let mut src = String::new(); + src_file + .read_to_string(&mut src) + .map_err(|_| Box::new(EvmTestError("src file read failed".to_string())))?; + src = src.replace("\"", "\\\""); + + Self::compile_from_src_string(&src, contract_name, opt) + } + + pub fn compile_from_src_string( + src: &String, + contract_name: &str, + opt: bool, + ) -> Result { + // Compile source file using solc + // Configuration: https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html + // TODO: Change output selection to only compile 'input' file + let solc_config = r#" + { + "language": "Solidity", + "sources": { "input.sol": { "content": "{src}" } }, + "settings": { + "optimizer": { "enabled": {opt} }, + "outputSelection": { + "*": { + "*": [ + "evm.bytecode.object", "abi" + ], + "": [ "*" ] } } + } + }"# + .replace("{opt}", &opt.to_string()) + .replace("{src}", &src); + Self::compile_from_config(&solc_config, contract_name) + } + + pub fn compile_from_config( + config: &String, + contract_name: &str, + ) -> Result { + // Compile source file using solc + // Configuration: https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html + let out = from_str::(&compile(config)) + .map_err(|_| Box::new(EvmTestError("solc compile failed".to_string())))?; + + if out["errors"].is_array() { + if out["errors"] + .as_array() + .unwrap() + .iter() + .any(|e| e["severity"] == "error") + { + return Err(Box::new(EvmTestError(format!( + "solc compiled with errors: {}", + out["errors"] + )))); + } + } + + let binary = { + let hex_code = out["contracts"]["input.sol"][contract_name]["evm"]["bytecode"] + ["object"] + .to_string() + .replace("\"", ""); + let binary = hex::decode(&hex_code) + .map_err(|_| Box::new(EvmTestError("decode hex binary failed".to_string())))?; + binary + }; + let abi = { + if out["contracts"]["input.sol"][contract_name]["abi"] == "null" { + return Err(Box::new(EvmTestError( + "solc compiled with null abi".to_string(), + ))); + } + let abi = ContractAbi::load( + out["contracts"]["input.sol"][contract_name]["abi"] + .to_string() + .as_bytes(), + ) + .map_err(|_| Box::new(EvmTestError("ethabi failed loading abi".to_string())))?; + abi + }; + + Ok(Contract { binary, abi }) + } + + pub fn encode_create_contract_bytes(&self, init: &[Token]) -> Result, Error> { + match &self.abi.constructor { + Some(constructor) => { + let binary = constructor + .encode_input(self.binary.clone().into(), init) + .map_err(|_| { + Box::new(EvmTestError( + "abi constructor failed to encode inputs".to_string(), + )) + })?; + Ok(binary.to_vec()) + }, + None => Ok(self.binary.clone()), + } + } + + pub fn encode_call_contract_bytes(&self, fn_name: &str, input: &[Token]) -> Result, Error> { + match self.abi.functions.get(fn_name) { + Some(f) => { + //let c = f[0].inputs.iter().map(|p| p.kind.clone()).collect::>(); + //println!("{:?}", c); + let call_binary = f[0].encode_input(input) + .map_err(|_| { + Box::new(EvmTestError( + "abi function failed to encode inputs".to_string(), + )) + })?; + Ok(call_binary.to_vec()) + }, + None => Err(Box::new(EvmTestError("abi does not include function".to_string()))), + } + } + +} diff --git a/zokrates_solidity_test/src/evm.rs b/zokrates_solidity_test/src/evm.rs new file mode 100644 index 00000000..6f65e30d --- /dev/null +++ b/zokrates_solidity_test/src/evm.rs @@ -0,0 +1,121 @@ +use primitive_types::U256; +use revm::{AccountInfo, InMemoryDB, Log, Return, TransactOut, TransactTo, EVM}; + +use crate::{address::Address, Error, EvmTestError}; + +#[derive(Debug)] +pub struct CallResult { + pub op_out: Return, + pub out: Vec, + pub gas: u64, + pub log_out: Vec, +} + +#[derive(Debug)] +pub struct CreateContractResult { + pub addr: Address, + pub gas: u64, +} + +pub struct Evm { + vm: EVM, +} + +impl Evm { + pub fn new() -> Self { + let mut vm = revm::new(); + vm.database(InMemoryDB::default()); + Self { vm } + } + + pub fn call(&mut self, input: Vec, addr: &Address, caller: &Address) -> Result { + self.vm.env.tx.caller = caller.as_ref().clone(); + self.vm.env.tx.transact_to = TransactTo::Call(addr.as_ref().clone()); + self.vm.env.tx.data = input.into(); + let (op_out, tx_out, gas, log_out) = self.vm.transact_commit(); + let out = match tx_out { + TransactOut::Call(out) => Ok(out.to_vec()), + _ => Err(Box::new(EvmTestError("call contract function failed".to_string()))), + }?; + Ok(CallResult { + op_out, + out, + gas, + log_out, + }) + } + + pub fn deploy( + &mut self, + contract: Vec, + deployer: &Address, + ) -> Result { + match self + .vm + .db() + .unwrap() + .cache() + .get_key_value(deployer.as_ref()) + { + Some(_) => { + self.vm.env.tx.caller = deployer.as_ref().clone(); + self.vm.env.tx.transact_to = TransactTo::create(); + self.vm.env.tx.data = contract.into(); + let (_, tx_out, gas, _) = self.vm.transact_commit(); + let contract_address = match tx_out { + TransactOut::Create(_, Some(addr)) => Ok(Address(addr)), + _ => Err(Box::new(EvmTestError("create contract failed".to_string()))), + }?; + Ok(CreateContractResult { + addr: contract_address, + gas, + }) + } + None => Err(Box::new(EvmTestError( + "deployer address not found".to_string(), + ))), + } + } + + pub fn create_account(&mut self, address: &Address, balance: impl Into) { + let acc = AccountInfo::from_balance(balance.into()); + self.vm + .db() + .unwrap() + .insert_cache(address.as_ref().clone(), acc); + } + + pub fn set_account_balance( + &mut self, + address: &Address, + balance: impl Into, + ) -> Result<(), Error> { + let mut acc = self + .vm + .db() + .unwrap() + .cache() + .get(address.as_ref()) + .ok_or(Box::new(EvmTestError( + "account address not found".to_string(), + )))? + .clone(); + acc.balance = balance.into(); + self.vm + .db() + .unwrap() + .insert_cache(address.as_ref().clone(), acc); + Ok(()) + } + + pub fn balance_of(&mut self, address: &Address) -> U256 { + match self.vm.db().unwrap().cache().get(address.as_ref()) { + Some(acc) => acc.balance, + None => 0.into(), + } + } + + pub fn get_account(&mut self, address: &Address) -> Option { + self.vm.db().unwrap().cache().get(address.as_ref()).cloned() + } +} diff --git a/zokrates_solidity_test/src/lib.rs b/zokrates_solidity_test/src/lib.rs new file mode 100644 index 00000000..887c0357 --- /dev/null +++ b/zokrates_solidity_test/src/lib.rs @@ -0,0 +1,82 @@ +use primitive_types::U256; + +use std::{ + error::Error as ErrorTrait, + fmt::{self, Debug}, +}; + +pub mod address; +pub mod contract; +pub mod evm; + +pub type Error = Box; + +#[derive(Debug)] +pub struct EvmTestError(String); + +impl ErrorTrait for EvmTestError { + fn source(self: &Self) -> Option<&(dyn ErrorTrait + 'static)> { + None + } +} + +impl fmt::Display for EvmTestError { + fn fmt(self: &Self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +pub fn to_be_bytes(n: &U256) -> [u8; 32] { + let mut input_bytes: [u8; 32] = [0; 32]; + n.to_big_endian(&mut input_bytes); + input_bytes +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + address::Address, + contract::Contract, + evm::{Evm}, + }; + use rand::{rngs::StdRng, SeedableRng}; + use ethabi::Token; + + #[test] + fn simple_storage_contract_test() { + let mut rng = StdRng::seed_from_u64(0u64); + + // Compile contract + let contract_path = format!( + "{}/contracts/simple_storage.sol", + env!("CARGO_MANIFEST_DIR") + ); + let contract = + Contract::compile_from_solidity_file(contract_path, "SimpleStorage", false).unwrap(); + + // Setup EVM + let mut evm = Evm::new(); + let deployer = Address::random(&mut rng); + evm.create_account(&deployer, 0); + + // Deploy contract + let create_result = evm.deploy(contract.encode_create_contract_bytes(&[]).unwrap(), &deployer).unwrap(); + let contract_addr = create_result.addr.clone(); + println!("Contract deploy gas cost: {}", create_result.gas); + + // Call get function on contract + let get_result = evm.call(contract.encode_call_contract_bytes("get", &[]).unwrap(), &contract_addr, &deployer).unwrap(); + assert_eq!(&get_result.out, &to_be_bytes(&U256::from(0))); + println!("{:?}", get_result); + + // Call set function on contract + let set_result = evm.call(contract.encode_call_contract_bytes("set", &[Token::Tuple(vec![Token::Uint(U256::from(40))])]).unwrap(), &contract_addr, &deployer).unwrap(); + println!("{:?}", set_result); + + // Call get function on contract + let get_result = evm.call(contract.encode_call_contract_bytes("get", &[]).unwrap(), &contract_addr, &deployer).unwrap(); + assert_eq!(&get_result.out, &to_be_bytes(&U256::from(40))); + println!("{:?}", get_result); + } +}