Last active
April 12, 2025 21:08
-
-
Save HarryR/752c5ba66517b4bdf9aa1704e12a3d1c to your computer and use it in GitHub Desktop.
Alternative to the Oasis `orc` tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 $ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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