risc0_build_ethereum/
lib.rs1use std::{
16 env, fs,
17 io::Write,
18 path::{Path, PathBuf},
19 process::{Command, Stdio},
20};
21
22use anyhow::{anyhow, bail, Context, Result};
23use risc0_build::GuestListEntry;
24
25const SOL_HEADER: &str = r#"// Copyright 2024 RISC Zero, Inc.
26//
27// Licensed under the Apache License, Version 2.0 (the "License");
28// you may not use this file except in compliance with the License.
29// You may obtain a copy of the License at
30//
31// http://www.apache.org/licenses/LICENSE-2.0
32//
33// Unless required by applicable law or agreed to in writing, software
34// distributed under the License is distributed on an "AS IS" BASIS,
35// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
36// See the License for the specific language governing permissions and
37// limitations under the License.
38//
39// SPDX-License-Identifier: Apache-2.0
40
41// This file is automatically generated
42
43"#;
44
45const IMAGE_ID_LIB_HEADER: &str = r#"pragma solidity ^0.8.20;
46
47library ImageID {
48"#;
49
50const ELF_LIB_HEADER: &str = r#"pragma solidity ^0.8.20;
51
52library Elf {
53"#;
54
55#[derive(Debug, Clone, Default)]
57#[non_exhaustive] pub struct Options {
59 pub image_id_sol_path: Option<PathBuf>,
61
62 pub elf_sol_path: Option<PathBuf>,
64}
65
66impl Options {
69 pub fn with_image_id_sol_path(mut self, path: impl AsRef<Path>) -> Self {
71 self.image_id_sol_path = Some(path.as_ref().to_owned());
72 self
73 }
74
75 pub fn with_elf_sol_path(mut self, path: impl AsRef<Path>) -> Self {
77 self.elf_sol_path = Some(path.as_ref().to_owned());
78 self
79 }
80}
81
82pub fn generate_solidity_files(guests: &[GuestListEntry], opts: &Options) -> Result<()> {
84 if env::var("RISC0_SKIP_BUILD").is_ok() {
86 return Ok(());
87 }
88 let image_id_file_path = opts
89 .image_id_sol_path
90 .as_ref()
91 .ok_or(anyhow!("path for image ID Solidity file must be provided"))?;
92 fs::write(image_id_file_path, generate_image_id_sol(guests)?)
93 .with_context(|| format!("failed to save changes to {}", image_id_file_path.display()))?;
94
95 let elf_sol_path = opts.elf_sol_path.as_ref().ok_or(anyhow!(
96 "path for guest ELFs Solidity file must be provided"
97 ))?;
98 fs::write(elf_sol_path, generate_elf_sol(guests)?)
99 .with_context(|| format!("failed to save changes to {}", image_id_file_path.display()))?;
100
101 Ok(())
102}
103
104pub fn generate_image_id_sol(guests: &[GuestListEntry]) -> Result<Vec<u8>> {
106 let image_ids: Vec<_> = guests
108 .iter()
109 .map(|guest| {
110 let name = guest.name.to_uppercase().replace('-', "_");
111 let image_id = guest.image_id;
112 format!("bytes32 public constant {name}_ID = bytes32(0x{image_id});")
113 })
114 .collect();
115
116 let image_id_lines = image_ids.join("\n");
117
118 let file_content = format!("{SOL_HEADER}{IMAGE_ID_LIB_HEADER}\n{image_id_lines}\n}}");
120 forge_fmt(file_content.as_bytes()).context("failed to format image ID file")
121}
122
123pub fn generate_elf_sol(guests: &[GuestListEntry]) -> Result<Vec<u8>> {
127 let elf_paths: Vec<_> = guests
129 .iter()
130 .map(|guest| {
131 let name = guest.name.to_uppercase().replace('-', "_");
132
133 let elf_path = guest.path.to_string();
134 format!("string public constant {name}_PATH = \"{elf_path}\";")
135 })
136 .collect();
137
138 let elf_path_lines = elf_paths.join("\n");
140 let file_content = format!("{SOL_HEADER}{ELF_LIB_HEADER}\n{elf_path_lines}\n}}");
141 forge_fmt(file_content.as_bytes()).context("failed to format image ID file")
142}
143
144fn forge_fmt(src: &[u8]) -> Result<Vec<u8>> {
146 let mut fmt_proc = Command::new("forge")
148 .args(["fmt", "-", "--raw"])
149 .stdin(Stdio::piped())
150 .stdout(Stdio::piped())
151 .spawn()
152 .context("failed to spawn forge fmt")?;
153
154 fmt_proc
156 .stdin
157 .take()
158 .context("failed to take forge fmt stdin handle")?
159 .write_all(src)
160 .context("failed to write to forge fmt stdin")?;
161
162 let fmt_out = fmt_proc
163 .wait_with_output()
164 .context("failed to run forge fmt")?;
165
166 if !fmt_out.status.success() {
167 bail!(
168 "forge fmt on image ID file content exited with status {}",
169 fmt_out.status,
170 );
171 }
172
173 Ok(fmt_out.stdout)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::forge_fmt;
179 use pretty_assertions::assert_eq;
180
181 const FORMATTED_SRC: &str = r#"// SPDX-License-Identifier: MIT
183pragma solidity ^0.8.20;
184
185contract Counter {
186 uint256 public count;
187
188 // Function to get the current count
189 function get() public view returns (uint256) {
190 return count;
191 }
192
193 // Function to increment count by 1
194 function inc() public {
195 count += 1;
196 }
197
198 // Function to decrement count by 1
199 function dec() public {
200 // This function will fail if count = 0
201 count -= 1;
202 }
203}
204"#;
205
206 const UNFORMATTED_SRC: &str = r#"
207// SPDX-License-Identifier: MIT
208pragma solidity ^0.8.20;
209
210contract Counter {
211 uint public count;
212
213 // Function to get the current count
214 function get() public view returns (uint) {
215 return count;
216 }
217
218 // Function to increment count by 1
219 function inc()
220 public {
221 count
222 +=
223 1;
224 }
225
226// Function to decrement count by 1
227 function dec() public {
228 // This function will fail if count = 0
229count-=1;
230 }
231}
232
233"#;
234
235 #[test]
236 fn forge_fmt_works() {
237 assert_eq!(
238 String::from_utf8(forge_fmt(UNFORMATTED_SRC.as_bytes()).unwrap()).unwrap(),
239 String::from_utf8(FORMATTED_SRC.as_bytes().to_owned()).unwrap()
240 );
241 }
242}