linera_execution/test_utils/
solidity.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Code for compiling solidity smart contracts for testing purposes.
5
6use std::{
7    fs::File,
8    io::Write,
9    path::{Path, PathBuf},
10    process::{Command, Stdio},
11};
12
13use anyhow::Context;
14use serde_json::Value;
15use tempfile::{tempdir, TempDir};
16
17use crate::{LINERA_SOL, LINERA_TYPES_SOL};
18
19fn write_compilation_json(path: &Path, file_name: &str) -> anyhow::Result<()> {
20    let mut source = File::create(path).unwrap();
21    writeln!(
22        source,
23        r#"
24{{
25  "language": "Solidity",
26  "sources": {{
27    "{file_name}": {{
28      "urls": ["./{file_name}"]
29    }}
30  }},
31  "settings": {{
32    "viaIR": true,
33    "outputSelection": {{
34      "*": {{
35        "*": ["evm.bytecode"]
36      }}
37    }}
38  }}
39}}
40"#
41    )?;
42    Ok(())
43}
44
45fn get_bytecode_path(path: &Path, file_name: &str, contract_name: &str) -> anyhow::Result<Vec<u8>> {
46    let config_path = path.join("config.json");
47    write_compilation_json(&config_path, file_name)?;
48    let config_file = File::open(config_path)?;
49
50    let output_path = path.join("result.json");
51    let output_file = File::create(output_path.clone())?;
52
53    let status = Command::new("solc")
54        .current_dir(path)
55        .arg("--standard-json")
56        .stdin(Stdio::from(config_file))
57        .stdout(Stdio::from(output_file))
58        .status()?;
59    assert!(status.success());
60
61    let contents = std::fs::read_to_string(output_path)?;
62    let json_data: serde_json::Value = serde_json::from_str(&contents)?;
63    let contracts = json_data
64        .get("contracts")
65        .with_context(|| format!("failed to get contracts in json_data={}", json_data))?;
66    let file_name_contract = contracts
67        .get(file_name)
68        .context("failed to get {file_name}")?;
69    let test_data = file_name_contract
70        .get(contract_name)
71        .context("failed to get contract_name={contract_name}")?;
72    let evm_data = test_data.get("evm").context("failed to get evm")?;
73    let bytecode = evm_data.get("bytecode").context("failed to get bytecode")?;
74    let object = bytecode.get("object").context("failed to get object")?;
75    let object = object.to_string();
76    let object = object.trim_matches(|c| c == '"').to_string();
77    Ok(hex::decode(&object)?)
78}
79
80pub fn get_bytecode(source_code: &str, contract_name: &str) -> anyhow::Result<Vec<u8>> {
81    let dir = tempdir().unwrap();
82    let path = dir.path();
83    if source_code.contains("Linera.sol") {
84        // The source code seems to import Linera.sol, so we import the relevant files.
85        for (file_name, literal_path) in [
86            ("Linera.sol", LINERA_SOL),
87            ("LineraTypes.sol", LINERA_TYPES_SOL),
88        ] {
89            let test_code_path = path.join(file_name);
90            let mut test_code_file = File::create(&test_code_path)?;
91            writeln!(test_code_file, "{}", literal_path)?;
92        }
93    }
94    let file_name = "test_code.sol";
95    let test_code_path = path.join(file_name);
96    let mut test_code_file = File::create(&test_code_path)?;
97    writeln!(test_code_file, "{}", source_code)?;
98    get_bytecode_path(path, file_name, contract_name)
99}
100
101pub fn load_solidity_example(path: &str) -> anyhow::Result<Vec<u8>> {
102    let source_code = std::fs::read_to_string(path)?;
103    let contract_name: &str = source_code
104        .lines()
105        .filter_map(|line| line.trim_start().strip_prefix("contract "))
106        .next()
107        .ok_or_else(|| anyhow::anyhow!("Not matching"))?;
108    let contract_name: &str = contract_name
109        .strip_suffix(" {")
110        .ok_or_else(|| anyhow::anyhow!("Not matching"))?;
111    get_bytecode(&source_code, contract_name)
112}
113
114pub fn temporary_write_evm_module(module: Vec<u8>) -> anyhow::Result<(PathBuf, TempDir)> {
115    let dir = tempfile::tempdir()?;
116    let path = dir.path();
117    let app_file = "app.json";
118    let app_path = path.join(app_file);
119    {
120        std::fs::write(app_path.clone(), &module)?;
121    }
122    let evm_contract = app_path.to_path_buf();
123    Ok((evm_contract, dir))
124}
125
126pub fn get_evm_contract_path(path: &str) -> anyhow::Result<(PathBuf, TempDir)> {
127    let module = load_solidity_example(path)?;
128    temporary_write_evm_module(module)
129}
130
131pub fn value_to_vec_u8(value: Value) -> Vec<u8> {
132    let mut vec: Vec<u8> = Vec::new();
133    for val in value.as_array().unwrap() {
134        let val = val.as_u64().unwrap();
135        let val = val as u8;
136        vec.push(val);
137    }
138    vec
139}
140
141pub fn read_evm_u64_entry(value: Value) -> u64 {
142    let vec = value_to_vec_u8(value);
143    let mut arr = [0_u8; 8];
144    arr.copy_from_slice(&vec[24..]);
145    u64::from_be_bytes(arr)
146}