linera_execution/test_utils/
solidity.rs1use std::{
7 fs::File,
8 io::Write,
9 path::{Path, PathBuf},
10 process::{Command, Stdio},
11};
12
13use anyhow::Context;
14use revm_primitives::{Address, U256};
15use serde_json::Value;
16use tempfile::{tempdir, TempDir};
17
18use crate::{LINERA_SOL, LINERA_TYPES_SOL};
19
20fn write_compilation_json(
21 path: &Path,
22 file_name: &str,
23 optimizer_runs: Option<u32>,
24) -> anyhow::Result<()> {
25 let optimizer_block = optimizer_runs
26 .map(|runs| {
27 format!(
28 r#""optimizer": {{
29 "enabled": true,
30 "runs": {runs}
31 }},
32 "#
33 )
34 })
35 .unwrap_or_default();
36 let mut source = File::create(path).unwrap();
37 writeln!(
38 source,
39 r#"
40{{
41 "language": "Solidity",
42 "sources": {{
43 "{file_name}": {{
44 "urls": ["./{file_name}"]
45 }}
46 }},
47 "settings": {{
48 "viaIR": true,
49 "evmVersion": "cancun",
50 {optimizer_block}"outputSelection": {{
51 "*": {{
52 "*": ["evm.bytecode"]
53 }}
54 }}
55 }}
56}}
57"#
58 )?;
59 Ok(())
60}
61
62fn get_bytecode_path(
63 path: &Path,
64 file_name: &str,
65 contract_name: &str,
66 optimizer_runs: Option<u32>,
67) -> anyhow::Result<Vec<u8>> {
68 let config_path = path.join("config.json");
69 write_compilation_json(&config_path, file_name, optimizer_runs)?;
70 let config_file = File::open(config_path)?;
71
72 let output_path = path.join("result.json");
73 let output_file = File::create(output_path.clone())?;
74
75 let status = Command::new("solc")
76 .current_dir(path)
77 .arg("--standard-json")
78 .stdin(Stdio::from(config_file))
79 .stdout(Stdio::from(output_file))
80 .status()?;
81 assert!(status.success());
82
83 let contents = std::fs::read_to_string(output_path)?;
84 let json_data: serde_json::Value = serde_json::from_str(&contents)?;
85 let contracts = json_data
86 .get("contracts")
87 .with_context(|| format!("failed to get contracts in json_data={json_data}"))?;
88 let file_name_contract = contracts
89 .get(file_name)
90 .context("failed to get {file_name}")?;
91 let test_data = file_name_contract
92 .get(contract_name)
93 .with_context(|| format!("failed to get contract_name={contract_name}"))?;
94 let evm_data = test_data
95 .get("evm")
96 .with_context(|| format!("failed to get evm in test_data={test_data}"))?;
97 let bytecode = evm_data
98 .get("bytecode")
99 .with_context(|| format!("failed to get bytecode in evm_data={evm_data}"))?;
100 let object = bytecode
101 .get("object")
102 .with_context(|| format!("failed to get object in bytecode={bytecode}"))?;
103 let object = object.to_string();
104 let object = object.trim_matches(|c| c == '"').to_string();
105 Ok(hex::decode(&object)?)
106}
107
108pub fn compile_solidity_contract(
110 source_code: &str,
111 file_name: &str,
112 contract_name: &str,
113 extra_sources: &[(&str, &str)],
114) -> anyhow::Result<Vec<u8>> {
115 compile_solidity_contract_with_options(
116 source_code,
117 file_name,
118 contract_name,
119 extra_sources,
120 None,
121 )
122}
123
124pub fn compile_solidity_contract_with_options(
127 source_code: &str,
128 file_name: &str,
129 contract_name: &str,
130 extra_sources: &[(&str, &str)],
131 optimizer_runs: Option<u32>,
132) -> anyhow::Result<Vec<u8>> {
133 let dir = tempdir().unwrap();
134 let path = dir.path();
135 for (extra_file_name, extra_source_code) in extra_sources {
136 let extra_code_path = path.join(extra_file_name);
137 let mut extra_code_file = File::create(&extra_code_path)?;
138 writeln!(extra_code_file, "{extra_source_code}")?;
139 }
140 if source_code.contains("Linera.sol") {
141 for (file_name, literal_path) in [
143 ("Linera.sol", LINERA_SOL),
144 ("LineraTypes.sol", LINERA_TYPES_SOL),
145 ] {
146 let test_code_path = path.join(file_name);
147 let mut test_code_file = File::create(&test_code_path)?;
148 writeln!(test_code_file, "{literal_path}")?;
149 }
150 }
151 if source_code.contains("@openzeppelin") {
152 let _output = Command::new("npm")
153 .args(["install", "@openzeppelin/contracts"])
154 .current_dir(path)
155 .output()?;
156 let _output = Command::new("mv")
157 .args(["node_modules/@openzeppelin", "@openzeppelin"])
158 .current_dir(path)
159 .output()?;
160 }
161 let test_code_path = path.join(file_name);
162 let mut test_code_file = File::create(&test_code_path)?;
163 writeln!(test_code_file, "{source_code}")?;
164 get_bytecode_path(path, file_name, contract_name, optimizer_runs)
165}
166
167pub fn get_bytecode(source_code: &str, contract_name: &str) -> anyhow::Result<Vec<u8>> {
169 compile_solidity_contract(source_code, "test_code.sol", contract_name, &[])
170}
171
172pub fn load_solidity_example(path: &str) -> anyhow::Result<Vec<u8>> {
174 let source_code = std::fs::read_to_string(path)?;
175 let contract_name: &str = source_code
176 .lines()
177 .find_map(|line| line.trim_start().strip_prefix("contract "))
178 .ok_or_else(|| anyhow::anyhow!("Not matching"))?;
179 let contract_name: &str = contract_name
180 .split_whitespace()
181 .next()
182 .ok_or_else(|| anyhow::anyhow!("No space found after the contract name"))?;
183 tracing::info!("load_solidity_example, contract_name={contract_name}");
184 get_bytecode(&source_code, contract_name)
185}
186
187pub fn load_solidity_example_by_name(path: &str, contract_name: &str) -> anyhow::Result<Vec<u8>> {
189 let source_code = std::fs::read_to_string(path)?;
190 get_bytecode(&source_code, contract_name)
191}
192
193pub fn temporary_write_evm_module(module: &[u8]) -> anyhow::Result<(PathBuf, TempDir)> {
195 let dir = tempfile::tempdir()?;
196 let path = dir.path();
197 let app_file = "app.json";
198 let app_path = path.join(app_file);
199 {
200 std::fs::write(app_path.clone(), module)?;
201 }
202 let evm_contract = app_path.to_path_buf();
203 Ok((evm_contract, dir))
204}
205
206pub fn get_evm_contract_path(path: &str) -> anyhow::Result<(PathBuf, TempDir)> {
208 let module = load_solidity_example(path)?;
209 temporary_write_evm_module(&module)
210}
211
212pub fn value_to_vec_u8(value: &Value) -> Vec<u8> {
214 let mut vec: Vec<u8> = Vec::new();
215 for val in value.as_array().unwrap() {
216 let val = val.as_u64().unwrap();
217 let val = val as u8;
218 vec.push(val);
219 }
220 vec
221}
222
223pub fn read_evm_u64_entry(value: &Value) -> u64 {
225 let vec = value_to_vec_u8(value);
226 let mut arr = [0_u8; 8];
227 arr.copy_from_slice(&vec[24..]);
228 u64::from_be_bytes(arr)
229}
230
231pub fn read_evm_u256_entry(value: &Value) -> U256 {
233 let result = value_to_vec_u8(value);
234 U256::from_be_slice(&result)
235}
236
237pub fn read_evm_address_entry(value: &Value) -> Address {
239 let vec = value_to_vec_u8(value);
240 let mut arr = [0_u8; 20];
241 arr.copy_from_slice(&vec[12..]);
242 Address::from_slice(&arr)
243}