5c91d077a5
The pezkuwi.io domain does not exist. All references have been updated to the correct pezkuwichain.io domain. Fixed files: - subxt examples (rpc.pezkuwichain.io) - subxt lib.rs documentation - contracts fixtures (homepage, authors email) - CHANGELOG.md (zagros-rpc.pezkuwichain.io)
324 lines
9.2 KiB
Rust
324 lines
9.2 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Compile contracts to wasm.
|
|
use anyhow::{bail, Context, Result};
|
|
use parity_wasm::elements::{deserialize_file, serialize_to_file, Internal};
|
|
use std::{
|
|
env, fs,
|
|
hash::Hasher,
|
|
path::{Path, PathBuf},
|
|
process::Command,
|
|
};
|
|
use twox_hash::XxHash32;
|
|
|
|
/// Read the file at `path` and return its hash as a hex string.
|
|
fn file_hash(path: &Path) -> String {
|
|
let data = fs::read(path).expect("file exists; qed");
|
|
let mut hasher = XxHash32::default();
|
|
hasher.write(&data);
|
|
hasher.write(include_bytes!("build.rs"));
|
|
let hash = hasher.finish();
|
|
format!("{:x}", hash)
|
|
}
|
|
|
|
/// A contract entry.
|
|
struct Entry {
|
|
/// The path to the contract source file.
|
|
path: PathBuf,
|
|
/// The hash of the contract source file.
|
|
hash: String,
|
|
}
|
|
|
|
impl Entry {
|
|
/// Create a new contract entry from the given path.
|
|
fn new(path: PathBuf) -> Self {
|
|
let hash = file_hash(&path);
|
|
Self { path, hash }
|
|
}
|
|
|
|
/// Return the path to the contract source file.
|
|
fn path(&self) -> &str {
|
|
self.path.to_str().expect("path is valid unicode; qed")
|
|
}
|
|
|
|
/// Return the name of the contract.
|
|
fn name(&self) -> &str {
|
|
self.path
|
|
.file_stem()
|
|
.expect("file exits; qed")
|
|
.to_str()
|
|
.expect("name is valid unicode; qed")
|
|
}
|
|
|
|
/// Return whether the contract has already been compiled.
|
|
fn is_cached(&self, out_dir: &Path) -> bool {
|
|
out_dir.join(self.name()).join(&self.hash).exists()
|
|
}
|
|
|
|
/// Update the cache file for the contract.
|
|
fn update_cache(&self, out_dir: &Path) -> Result<()> {
|
|
let cache_dir = out_dir.join(self.name());
|
|
|
|
// clear the cache dir if it exists
|
|
if cache_dir.exists() {
|
|
fs::remove_dir_all(&cache_dir)?;
|
|
}
|
|
|
|
// re-populate the cache dir with the new hash
|
|
fs::create_dir_all(&cache_dir)?;
|
|
fs::write(out_dir.join(&self.hash), "")?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Return the name of the output wasm file.
|
|
fn out_wasm_filename(&self) -> String {
|
|
format!("{}.wasm", self.name())
|
|
}
|
|
}
|
|
|
|
/// Collect all contract entries from the given source directory.
|
|
/// Contracts that have already been compiled are filtered out.
|
|
fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec<Entry> {
|
|
fs::read_dir(contracts_dir)
|
|
.expect("src dir exists; qed")
|
|
.filter_map(|file| {
|
|
let path = file.expect("file exists; qed").path();
|
|
if path.extension().map_or(true, |ext| ext != "rs") {
|
|
return None;
|
|
}
|
|
|
|
let entry = Entry::new(path);
|
|
if entry.is_cached(out_dir) {
|
|
None
|
|
} else {
|
|
Some(entry)
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
/// Create a `Cargo.toml` to compile the given contract entries.
|
|
fn create_cargo_toml<'a>(
|
|
fixtures_dir: &Path,
|
|
root_cargo_toml: &Path,
|
|
entries: impl Iterator<Item = &'a Entry>,
|
|
output_dir: &Path,
|
|
) -> Result<()> {
|
|
let root_toml: toml::Value = toml::from_str(&fs::read_to_string(root_cargo_toml)?)?;
|
|
// Template embedded to avoid include_str! path issues during crates.io publish
|
|
const CARGO_TOML_TEMPLATE: &str = r#"[package]
|
|
name = "contracts"
|
|
version = "0.6.3"
|
|
edition = "2021"
|
|
description = "Pezkuwi SDK component: contracts"
|
|
repository = "https://github.com/pezkuwichain/pezkuwi-sdk"
|
|
homepage = "https://pezkuwichain.io"
|
|
authors = ["Pezkuwi Chain <admin@pezkuwichain.io>"]
|
|
license = "Apache-2.0"
|
|
[[bin]]
|
|
|
|
[dependencies]
|
|
common = { package = 'pezpallet-contracts-fixtures-common', path = "" }
|
|
polkavm-derive = { version = "" }
|
|
uapi = { package = 'pezpallet-contracts-uapi', path = "", default-features = false }
|
|
|
|
[profile.release]
|
|
opt-level = 3
|
|
lto = true
|
|
codegen-units = 1
|
|
"#;
|
|
let mut cargo_toml: toml::Value = toml::from_str(CARGO_TOML_TEMPLATE)?;
|
|
let mut set_dep = |name, path| -> Result<()> {
|
|
cargo_toml["dependencies"][name]["path"] = toml::Value::String(
|
|
fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(),
|
|
);
|
|
Ok(())
|
|
};
|
|
set_dep("uapi", "../uapi")?;
|
|
set_dep("common", "./contracts/common")?;
|
|
cargo_toml["dependencies"]["polkavm-derive"]["version"] =
|
|
root_toml["workspace"]["dependencies"]["polkavm-derive"].clone();
|
|
|
|
cargo_toml["bin"] = toml::Value::Array(
|
|
entries
|
|
.map(|entry| {
|
|
let name = entry.name();
|
|
let path = entry.path();
|
|
toml::Value::Table(toml::toml! {
|
|
name = name
|
|
path = path
|
|
})
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
);
|
|
|
|
let cargo_toml = toml::to_string_pretty(&cargo_toml)?;
|
|
fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into)
|
|
}
|
|
|
|
/// Invoke `cargo fmt` to check that fixtures files are formatted.
|
|
fn invoke_cargo_fmt<'a>(
|
|
config_path: &Path,
|
|
files: impl Iterator<Item = &'a Path>,
|
|
contract_dir: &Path,
|
|
) -> Result<()> {
|
|
// If rustfmt is not installed, skip the check.
|
|
if !Command::new("rustup")
|
|
.args(["nightly-2024-04-10", "run", "rustfmt", "--version"])
|
|
.output()
|
|
.map_or(false, |o| o.status.success())
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
let fmt_res = Command::new("rustup")
|
|
.args(["nightly-2024-04-10", "run", "rustfmt", "--check", "--config-path"])
|
|
.arg(config_path)
|
|
.args(files)
|
|
.output()
|
|
.expect("failed to execute process");
|
|
|
|
if fmt_res.status.success() {
|
|
return Ok(());
|
|
}
|
|
|
|
let stdout = String::from_utf8_lossy(&fmt_res.stdout);
|
|
let stderr = String::from_utf8_lossy(&fmt_res.stderr);
|
|
eprintln!("{}\n{}", stdout, stderr);
|
|
eprintln!(
|
|
"Fixtures files are not formatted.\n
|
|
Please run `rustup nightly-2024-04-10 run rustfmt --config-path {} {}/*.rs`",
|
|
config_path.display(),
|
|
contract_dir.display()
|
|
);
|
|
|
|
anyhow::bail!("Fixtures files are not formatted")
|
|
}
|
|
|
|
/// Build contracts for wasm.
|
|
fn invoke_wasm_build(current_dir: &Path) -> Result<()> {
|
|
let encoded_rustflags = [
|
|
"-Clink-arg=-zstack-size=65536",
|
|
"-Clink-arg=--import-memory",
|
|
"-Clinker-plugin-lto",
|
|
"-Ctarget-cpu=mvp",
|
|
"-Dwarnings",
|
|
]
|
|
.join("\x1f");
|
|
|
|
let build_res = Command::new(env::var("CARGO")?)
|
|
.current_dir(current_dir)
|
|
.env("CARGO_TARGET_DIR", current_dir.join("target").display().to_string())
|
|
.env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags)
|
|
.env("RUSTC_BOOTSTRAP", "1")
|
|
.args(["build", "--release", "--target=wasm32-unknown-unknown"])
|
|
.args(["-Z", "build-std=core,alloc"])
|
|
.output()
|
|
.expect("failed to execute process");
|
|
|
|
if build_res.status.success() {
|
|
return Ok(());
|
|
}
|
|
|
|
let stderr = String::from_utf8_lossy(&build_res.stderr);
|
|
eprintln!("{}", stderr);
|
|
bail!("Failed to build wasm contracts");
|
|
}
|
|
|
|
/// Post-process the compiled wasm contracts.
|
|
fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> {
|
|
let mut module =
|
|
deserialize_file(input_path).with_context(|| format!("Failed to read {:?}", input_path))?;
|
|
if let Some(section) = module.export_section_mut() {
|
|
section.entries_mut().retain(|entry| {
|
|
matches!(entry.internal(), Internal::Function(_))
|
|
&& (entry.field() == "call" || entry.field() == "deploy")
|
|
});
|
|
}
|
|
|
|
serialize_to_file(output_path, module).map_err(Into::into)
|
|
}
|
|
|
|
/// Write the compiled contracts to the given output directory.
|
|
fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec<Entry>) -> Result<()> {
|
|
for entry in entries {
|
|
let wasm_output = entry.out_wasm_filename();
|
|
post_process_wasm(
|
|
&build_dir.join("target/wasm32-unknown-unknown/release").join(&wasm_output),
|
|
&out_dir.join(&wasm_output),
|
|
)?;
|
|
|
|
entry.update_cache(out_dir)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns the root path of the wasm workspace.
|
|
fn find_workspace_root(current_dir: &Path) -> Option<PathBuf> {
|
|
let mut current_dir = current_dir.to_path_buf();
|
|
|
|
while current_dir.parent().is_some() {
|
|
if current_dir.join("Cargo.toml").exists() {
|
|
let cargo_toml_contents =
|
|
std::fs::read_to_string(current_dir.join("Cargo.toml")).ok()?;
|
|
if cargo_toml_contents.contains("[workspace]") {
|
|
return Some(current_dir);
|
|
}
|
|
}
|
|
|
|
current_dir.pop();
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into();
|
|
let contracts_dir = fixtures_dir.join("contracts");
|
|
let out_dir: PathBuf = env::var("OUT_DIR")?.into();
|
|
|
|
// During crates.io package verification, workspace root may not exist.
|
|
// In that case, skip contract compilation (contracts are pre-compiled in src/).
|
|
let Some(workspace_root) = find_workspace_root(&fixtures_dir) else {
|
|
eprintln!("Note: Workspace root not found, skipping contract compilation (expected during crates.io verification)");
|
|
return Ok(());
|
|
};
|
|
let root_cargo_toml = workspace_root.join("Cargo.toml");
|
|
|
|
let entries = collect_entries(&contracts_dir, &out_dir);
|
|
if entries.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let tmp_dir = tempfile::tempdir()?;
|
|
let tmp_dir_path = tmp_dir.path();
|
|
|
|
create_cargo_toml(&fixtures_dir, &root_cargo_toml, entries.iter(), tmp_dir.path())?;
|
|
invoke_cargo_fmt(
|
|
&workspace_root.join(".rustfmt.toml"),
|
|
entries.iter().map(|entry| &entry.path as _),
|
|
&contracts_dir,
|
|
)?;
|
|
|
|
invoke_wasm_build(tmp_dir_path)?;
|
|
|
|
write_output(tmp_dir_path, &out_dir, entries)?;
|
|
Ok(())
|
|
}
|