Skip to content

Instantly share code, notes, and snippets.

@HarryR
Last active April 12, 2025 21:08
Show Gist options
  • Save HarryR/752c5ba66517b4bdf9aa1704e12a3d1c to your computer and use it in GitHub Desktop.
Save HarryR/752c5ba66517b4bdf9aa1704e12a3d1c to your computer and use it in GitHub Desktop.
Alternative to the Oasis `orc` tool
[package]
name = "orc-builder"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.4", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10"
zip = "0.6"
VERSION := $(shell cargo pkgid | cut -d# -f2)
BINARY := target/release/my_binary
ORC_OUTPUT := target/my_package-$(VERSION).orc
.PHONY: all
all: $(ORC_OUTPUT)
$(BINARY):
cargo build --release
$(ORC_OUTPUT): $(BINARY)
orc-builder \
--out $@ \
--version $(VERSION) \
--name my_package \
--runtime sapphire-mainnet \
--elf $
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::process;
use clap::Parser;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha512_256};
use zip::{write::FileOptions, ZipWriter};
#[derive(Parser, Debug)]
#[command(author, version, about = "Build Oasis .orc file")]
struct Args {
/// Output .orc file path
#[arg(long, required = true)]
out: PathBuf,
/// Version number in x.x.x format
#[arg(long, required = true)]
version: String,
/// Name of ROFL package
#[arg(long, required = true)]
name: String,
/// Runtime ID
#[arg(long)]
runtime: Option<String>,
/// Path to ELF file
#[arg(long)]
elf: Option<PathBuf>,
/// Path to SGXS file
#[arg(long)]
sgxs: Option<PathBuf>,
/// Path to signature file
#[arg(long)]
sig: Option<PathBuf>,
}
#[derive(Serialize, Deserialize)]
struct Version {
#[serde(skip_serializing_if = "Option::is_none")]
major: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
minor: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
patch: Option<u32>,
}
#[derive(Serialize)]
struct SgxConfig {
executable: String,
signature: String,
}
#[derive(Serialize)]
struct Component {
kind: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
executable: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
sgx: Option<SgxConfig>,
}
#[derive(Serialize)]
struct Manifest {
name: String,
id: String,
version: Version,
components: Vec<Component>,
digests: HashMap<String, String>,
}
fn parse_version(version_str: &str) -> Result<Version, Box<dyn std::error::Error>> {
let parts: Vec<u32> = version_str
.split('.')
.map(|x| x.parse::<u32>())
.collect::<Result<Vec<u32>, _>>()?;
if parts.len() != 3 {
return Err("Version must be in x.x.x format".into());
}
Ok(Version {
major: Some(parts[0]),
minor: Some(parts[1]),
patch: Some(parts[2]),
})
}
fn calculate_digest(path: &PathBuf) -> Result<String, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let mut hasher = Sha512_256::new();
hasher.update(&buffer);
Ok(format!("{:x}", hasher.finalize()))
}
fn get_runtime_id(runtime: &str) -> Result<String, Box<dyn std::error::Error>> {
match runtime {
"sapphire-testnet" => Ok("000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c".to_string()),
"sapphire-mainnet" => Ok("000000000000000000000000000000000000000000000000f80306c9858e7279".to_string()),
"sapphire-localnet" => Ok("8000000000000000000000000000000000000000000000000000000000000000".to_string()),
_ => Err("Unknown runtime ID!".into()),
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let version = parse_version(&args.version)?;
let mut infiles = HashMap::new();
if let Some(elf) = args.elf.as_ref() {
infiles.insert("app.elf".to_string(), elf.clone());
}
if let Some(sgxs) = args.sgxs.as_ref() {
infiles.insert("app.sgxs".to_string(), sgxs.clone());
if args.sig.is_none() {
let mut sig_path = sgxs.clone();
sig_path.set_extension("sig");
infiles.insert("app.sig".to_string(), sig_path);
}
}
if let Some(sig) = args.sig.as_ref() {
infiles.insert("app.sig".to_string(), sig.clone());
}
let mut digests = HashMap::new();
for (k, v) in &infiles {
digests.insert(k.clone(), calculate_digest(v)?);
}
let runtime_id = if let Some(runtime) = args.runtime {
get_runtime_id(&runtime)?
} else {
"".to_string()
};
let mut component = Component {
kind: "rofl".to_string(),
name: args.name.clone(),
executable: None,
sgx: None,
};
if infiles.contains_key("app.elf") {
component.executable = Some("app.elf".to_string());
}
if infiles.contains_key("app.sgxs") {
component.sgx = Some(SgxConfig {
executable: "app.sgxs".to_string(),
signature: "app.sig".to_string(),
});
}
let manifest = Manifest {
name: args.name,
id: runtime_id,
version,
components: vec![component],
digests,
};
let file = File::create(args.out)?;
let mut zip = ZipWriter::new(file);
let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);
// Write manifest
zip.start_file("META-INF/MANIFEST.MF", options)?;
zip.write_all(&serde_json::to_vec(&manifest)?)?;
// Write files
for (k, v) in infiles {
zip.start_file(&k, options)?;
let mut file = File::open(v)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
}
zip.finish()?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment