Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust: ['1.91', '1.92']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
toolchain: 1.92
components: rustfmt, clippy, rust-src
targets: riscv64imac-unknown-none-elf
- name: Install solc
run: |
SOLC_VERSION=0.8.26
curl -L https://github.com/ethereum/solidity/releases/download/v${SOLC_VERSION}/solc-static-linux \
-o solc
chmod +x solc
sudo mv solc /usr/local/bin/solc
- name: Install Rust nightly for scaffold tests
run: |
rustup toolchain install nightly --component rust-src --profile minimal
- name: Run formatting check
run: cargo fmt --check
- name: Run clippy
Expand Down
15 changes: 13 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [
resolver = "2"

[workspace.package]
version = "0.2.2"
version = "0.2.3"
edition = "2024"
license = "MIT"
repository = "https://github.com/paritytech/cargo-pvm-contract"
Expand All @@ -22,5 +22,6 @@ polkavm-linker = "0.30.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tiny-keccak = { version = "2.0", features = ["keccak"] }
convert_case = "0.6"
toml_edit = "0.22"
inquire = "0.7"
41 changes: 1 addition & 40 deletions crates/cargo-pvm-contract-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,7 @@ fn build_elf(
profile: &Profile,
bins: &[String],
) -> Result<()> {
let immediate_abort = check_immediate_abort_support()?;

let rustflags = if immediate_abort {
"-Zunstable-options -Cpanic=immediate-abort"
} else {
""
};
let rustflags = "-Zunstable-options -Cpanic=immediate-abort";

let mut args = polkavm_linker::TargetJsonArgs::default();
args.is_64_bit = true;
Expand Down Expand Up @@ -229,10 +223,6 @@ fn build_elf(
.arg(&target_json)
.arg("-Zbuild-std=core,alloc");

if !immediate_abort {
cmd.arg("-Zbuild-std-features=panic_immediate_abort");
}

for bin in bins {
cmd.arg("--bin").arg(bin);
}
Expand All @@ -249,35 +239,6 @@ fn build_elf(
Ok(())
}

/// Check if rustc supports immediate abort (>= 1.92).
fn check_immediate_abort_support() -> Result<bool> {
let output = Command::new("rustc")
.arg("--version")
.output()
.context("Failed to run rustc --version")?;

let version_str = String::from_utf8(output.stdout).context("Invalid rustc version output")?;

let version = version_str
.split_whitespace()
.nth(1)
.context("Unexpected rustc version format")?;

let mut parts = version.split('.');
let major: u32 = parts
.next()
.context("Missing major version")?
.parse()
.context("Invalid major version")?;
let minor: u32 = parts
.next()
.context("Missing minor version")?
.parse()
.context("Invalid minor version")?;

Ok(major > 1 || (major == 1 && minor >= 92))
}

/// Link an ELF binary to PolkaVM bytecode.
fn link_to_polkavm(elf_path: &Path, output_path: &Path) -> Result<()> {
let elf_bytes = fs::read(elf_path)
Expand Down
2 changes: 2 additions & 0 deletions crates/cargo-pvm-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ serde_json = { workspace = true }
inquire = { workspace = true }
tiny-keccak = { workspace = true }
askama = { workspace = true }
polkavm-linker = { workspace = true }
convert_case = { workspace = true }

[dev-dependencies]
assert_cmd = "2.0"
Expand Down
112 changes: 67 additions & 45 deletions crates/cargo-pvm-contract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ use clap::{Parser, Subcommand, ValueEnum};
use include_dir::{Dir, include_dir};
use inquire::{Select, Text};
use log::debug;
use std::{
fs,
path::{Path, PathBuf},
};
use std::path::PathBuf;

mod scaffold;

Expand Down Expand Up @@ -77,24 +74,58 @@ impl std::fmt::Display for MemoryModel {
#[derive(Debug, Clone, PartialEq, Eq)]
struct ExampleContract {
name: String,
filename: String,
folder: String,
sol_filename: String,
rust_no_alloc: String,
rust_with_alloc: String,
}

impl ExampleContract {
fn from_path(path: &Path) -> Option<Self> {
if path.extension().and_then(|ext| ext.to_str()) != Some("sol") {
return None;
}

let filename = path.file_name()?.to_str()?.to_string();
let name = path.file_stem()?.to_str()?.to_string();
Some(Self { name, filename })
fn from_dir(dir: &Dir) -> Option<Self> {
let sol_file = dir
.files()
.find(|file| file.path().extension().and_then(|ext| ext.to_str()) == Some("sol"))?;
let sol_filename = sol_file.path().file_name()?.to_str()?.to_string();
let name = sol_file.path().file_stem()?.to_str()?.to_string();

let rust_no_alloc = dir
.files()
.find(|file| {
file.path()
.file_name()
.and_then(|filename| filename.to_str())
.is_some_and(|filename| filename.ends_with("_no_alloc.rs"))
})?
.path()
.file_name()?
.to_str()?
.to_string();
let rust_with_alloc = dir
.files()
.find(|file| {
file.path()
.file_name()
.and_then(|filename| filename.to_str())
.is_some_and(|filename| filename.ends_with("_with_alloc.rs"))
})?
.path()
.file_name()?
.to_str()?
.to_string();

Some(Self {
name,
folder: dir.path().to_str()?.to_string(),
sol_filename,
rust_no_alloc,
rust_with_alloc,
})
}

fn matches(&self, query: &str) -> bool {
let query = query.trim().to_ascii_lowercase();
let name = self.name.to_ascii_lowercase();
let filename = self.filename.to_ascii_lowercase();
let filename = self.sol_filename.to_ascii_lowercase();
query == name || query == filename
}
}
Expand All @@ -110,8 +141,8 @@ fn load_examples() -> Result<Vec<ExampleContract>> {
.get_dir("examples")
.ok_or_else(|| anyhow::anyhow!("Examples directory not found in templates"))?;
let mut examples: Vec<ExampleContract> = examples_dir
.files()
.filter_map(|file| ExampleContract::from_path(file.path()))
.dirs()
.filter_map(ExampleContract::from_dir)
.collect();

examples.sort_by(|left, right| left.name.cmp(&right.name));
Expand Down Expand Up @@ -176,7 +207,7 @@ fn init_command(args: PvmContractArgs) -> Result<()> {
check_dir_exists(&contract_name)?;
debug!(
"Initializing from example: {} with memory model: {:?}",
example.filename, memory_model
example.sol_filename, memory_model
);

init_from_example(&example, &contract_name, memory_model)
Expand Down Expand Up @@ -264,39 +295,30 @@ fn init_from_example(
contract_name: &str,
memory_model: MemoryModel,
) -> Result<()> {
// Get the embedded example .sol file
let example_path = format!("examples/{}", example.filename);
let example_file = TEMPLATES_DIR
.get_file(&example_path)
.ok_or_else(|| anyhow::anyhow!("Example file not found: {}", example_path))?;

// Write to a temporary file
let temp_dir = std::env::temp_dir();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.context("Failed to read system time")?
.as_nanos();
let example_temp_dir = temp_dir.join(format!("cargo-pvm-contract-{timestamp}"));
fs::create_dir_all(&example_temp_dir).with_context(|| {
format!("Failed to create temporary directory for example: {example_temp_dir:?}")
})?;
let temp_sol_path = example_temp_dir.join(example.filename.as_str());
fs::write(&temp_sol_path, example_file.contents())
.with_context(|| format!("Failed to write temporary .sol file: {:?}", temp_sol_path))?;
let sol_path = format!("{}/{}", example.folder, example.sol_filename);
let sol_file = TEMPLATES_DIR
.get_file(&sol_path)
.ok_or_else(|| anyhow::anyhow!("Example file not found: {sol_path}"))?;

let use_alloc = memory_model == MemoryModel::AllocWithAlloy;
let rust_example_name = if use_alloc {
example.rust_with_alloc.as_str()
} else {
example.rust_no_alloc.as_str()
};

// Use scaffold to initialize from the temp file
let result = scaffold::init_from_solidity_file(
temp_sol_path.to_str().unwrap(),
let rust_path = format!("{}/{}", example.folder, rust_example_name);
let rust_file = TEMPLATES_DIR
.get_file(&rust_path)
.ok_or_else(|| anyhow::anyhow!("Example file not found: {rust_path}"))?;

scaffold::init_from_example_files(
sol_file.contents(),
&example.sol_filename,
rust_file.contents(),
contract_name,
use_alloc,
);

// Clean up temp file
let _ = fs::remove_dir_all(&example_temp_dir);

result
)
}

fn check_dir_exists(contract_name: &str) -> Result<()> {
Expand Down
Loading