diff --git a/.github/workflows/networks-tests.yml b/.github/workflows/networks-tests.yml index 7fde517b..ee095df6 100644 --- a/.github/workflows/networks-tests.yml +++ b/.github/workflows/networks-tests.yml @@ -30,6 +30,7 @@ jobs: run: | GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \ -p bitcoin-serai \ + -p build-solidity-contracts \ -p alloy-simple-request-transport \ -p serai-ethereum-relayer \ -p monero-io \ diff --git a/Cargo.lock b/Cargo.lock index 55108241..f4584f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1318,6 +1318,10 @@ dependencies = [ "semver 0.6.0", ] +[[package]] +name = "build-solidity-contracts" +version = "0.1.0" + [[package]] name = "bumpalo" version = "3.16.0" @@ -8677,6 +8681,7 @@ name = "serai-processor-ethereum-contracts" version = "0.1.0" dependencies = [ "alloy-sol-types", + "build-solidity-contracts", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f06d76ef..08e0aabe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ members = [ "networks/bitcoin", + "networks/ethereum/build-contracts", "networks/ethereum/alloy-simple-request-transport", "networks/ethereum/relayer", diff --git a/networks/ethereum/build-contracts/Cargo.toml b/networks/ethereum/build-contracts/Cargo.toml new file mode 100644 index 00000000..cb47a28d --- /dev/null +++ b/networks/ethereum/build-contracts/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "build-solidity-contracts" +version = "0.1.0" +description = "A helper function to build Solidity contracts" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/networks/ethereum/build-contracts" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/networks/ethereum/build-contracts/LICENSE b/networks/ethereum/build-contracts/LICENSE new file mode 100644 index 00000000..41d5a261 --- /dev/null +++ b/networks/ethereum/build-contracts/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2022-2024 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/networks/ethereum/build-contracts/README.md b/networks/ethereum/build-contracts/README.md new file mode 100644 index 00000000..437f15c2 --- /dev/null +++ b/networks/ethereum/build-contracts/README.md @@ -0,0 +1,4 @@ +# Build Solidity Contracts + +A helper function to build Solidity contracts. This is intended to be called +from within build scripts. diff --git a/networks/ethereum/build-contracts/src/lib.rs b/networks/ethereum/build-contracts/src/lib.rs new file mode 100644 index 00000000..c546b111 --- /dev/null +++ b/networks/ethereum/build-contracts/src/lib.rs @@ -0,0 +1,88 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![deny(missing_docs)] + +use std::{path::PathBuf, fs, process::Command}; + +/// Build contracts placed in `contracts/`, outputting to `artifacts/`. +/// +/// Requires solc 0.8.25. +pub fn build(contracts_path: &str, artifacts_path: &str) -> Result<(), String> { + println!("cargo:rerun-if-changed={contracts_path}/*"); + println!("cargo:rerun-if-changed={artifacts_path}/*"); + + for line in String::from_utf8( + Command::new("solc") + .args(["--version"]) + .output() + .map_err(|_| "couldn't fetch solc output".to_string())? + .stdout, + ) + .map_err(|_| "solc stdout wasn't UTF-8")? + .lines() + { + if let Some(version) = line.strip_prefix("Version: ") { + let version = + version.split('+').next().ok_or_else(|| "no value present on line".to_string())?; + if version != "0.8.25" { + Err(format!("version was {version}, 0.8.25 required"))? + } + } + } + + #[rustfmt::skip] + let args = [ + "--base-path", ".", + "-o", "./artifacts", "--overwrite", + "--bin", "--abi", + "--via-ir", "--optimize", + "--no-color", + ]; + let mut args = args.into_iter().map(str::to_string).collect::>(); + + let mut queue = vec![PathBuf::from(contracts_path)]; + while let Some(folder) = queue.pop() { + for entry in fs::read_dir(folder).map_err(|e| format!("couldn't read directory: {e:?}"))? { + let entry = entry.map_err(|e| format!("couldn't read directory in entry: {e:?}"))?; + let kind = entry.file_type().map_err(|e| format!("couldn't fetch file type: {e:?}"))?; + if kind.is_dir() { + queue.push(entry.path()); + } + + if kind.is_file() && + entry + .file_name() + .into_string() + .map_err(|_| "file name wasn't a valid UTF-8 string".to_string())? + .ends_with(".sol") + { + args.push( + entry + .path() + .into_os_string() + .into_string() + .map_err(|_| "file path wasn't a valid UTF-8 string".to_string())?, + ); + } + + // We on purposely ignore symlinks to avoid recursive structures + } + } + + let solc = Command::new("solc") + .args(args) + .output() + .map_err(|_| "couldn't fetch solc output".to_string())?; + let stderr = + String::from_utf8(solc.stderr).map_err(|_| "solc stderr wasn't UTF-8".to_string())?; + if !solc.status.success() { + Err(format!("solc didn't successfully execute: {stderr}"))?; + } + for line in stderr.lines() { + if line.contains("Error:") { + Err(format!("solc output had error: {stderr}"))?; + } + } + + Ok(()) +} diff --git a/processor/ethereum/contracts/Cargo.toml b/processor/ethereum/contracts/Cargo.toml index 87beba08..f09eb938 100644 --- a/processor/ethereum/contracts/Cargo.toml +++ b/processor/ethereum/contracts/Cargo.toml @@ -18,3 +18,6 @@ workspace = true [dependencies] alloy-sol-types = { version = "0.8", default-features = false } + +[build-dependencies] +build-solidity-contracts = { path = "../../../networks/ethereum/build-contracts" } diff --git a/processor/ethereum/contracts/build.rs b/processor/ethereum/contracts/build.rs index fe79fcc1..8e310b60 100644 --- a/processor/ethereum/contracts/build.rs +++ b/processor/ethereum/contracts/build.rs @@ -1,45 +1,3 @@ -use std::process::Command; - fn main() { - println!("cargo:rerun-if-changed=contracts/*"); - println!("cargo:rerun-if-changed=artifacts/*"); - - for line in String::from_utf8(Command::new("solc").args(["--version"]).output().unwrap().stdout) - .unwrap() - .lines() - { - if let Some(version) = line.strip_prefix("Version: ") { - let version = version.split('+').next().unwrap(); - assert_eq!(version, "0.8.25"); - } - } - - #[rustfmt::skip] - let args = [ - "--base-path", ".", - "-o", "./artifacts", "--overwrite", - "--bin", "--abi", - "--via-ir", "--optimize", - - "./contracts/IERC20.sol", - - "./contracts/Schnorr.sol", - "./contracts/Deployer.sol", - "./contracts/Sandbox.sol", - "./contracts/Router.sol", - - "./contracts/tests/Schnorr.sol", - "./contracts/tests/ERC20.sol", - - "--no-color", - ]; - let solc = Command::new("solc").args(args).output().unwrap(); - assert!(solc.status.success()); - let stderr = String::from_utf8(solc.stderr).unwrap(); - for line in stderr.lines() { - if line.contains("Error:") { - println!("{stderr}"); - panic!() - } - } + build_solidity_contracts::build("contracts", "artifacts").unwrap(); }