use std::{
fs::{File, OpenOptions},
io,
io::Write,
os::unix::io::{AsRawFd, FromRawFd},
};
use alloy::{
hex::FromHex,
primitives::{Address, Bytes, Signature, U256},
sol_types::SolValue,
};
use anyhow::{bail, ensure, Context, Result};
use boundless_cli::{DefaultProver, OrderFulfilled};
use boundless_market::contracts::{eip712_domain, ProofRequest};
use clap::Parser;
use url::Url;
#[derive(Parser, Debug)]
#[clap(author, version, about = "Utility for use with Forge FFI cheatcode. See https://getfoundry.sh/reference/cheatcodes/ffi", long_about = None)]
struct MainArgs {
#[clap(long)]
set_builder_url: String,
#[clap(long)]
assessor_url: String,
#[clap(long)]
prover_address: Address,
#[clap(long)]
boundless_market_address: Address,
#[clap(long)]
chain_id: U256,
#[clap(long)]
request: String,
#[clap(long)]
signature: String,
#[clap(long, env = "IPFS_GATEWAY")]
ipfs_gateway: Option<String>,
#[clap(long, env = "PINATA_JWT")]
pinata_jwt: Option<String>,
}
async fn fetch_url(url_str: impl AsRef<str>, token: Option<String>) -> anyhow::Result<Vec<u8>> {
tracing::debug!("Fetching URL: {}", url_str.as_ref());
let url = Url::parse(url_str.as_ref())?;
match url.scheme() {
"http" | "https" => fetch_http(&url, token).await,
"file" => fetch_file(&url).await,
_ => bail!("unsupported URL scheme: {}", url.scheme()),
}
}
async fn fetch_http(url: &Url, token: Option<String>) -> anyhow::Result<Vec<u8>> {
let response = if let Some(jwt) = token {
reqwest::Client::new().get(url.as_str()).bearer_auth(jwt).send().await?
} else {
reqwest::get(url.as_str()).await?
};
let status = response.status();
if !status.is_success() {
bail!("HTTP request failed with status: {}", status);
}
Ok(response.bytes().await?.to_vec())
}
async fn fetch_file(url: &Url) -> anyhow::Result<Vec<u8>> {
let path = std::path::Path::new(url.path());
let data = tokio::fs::read(path).await?;
Ok(data)
}
#[tokio::main]
async fn main() -> Result<()> {
let args = MainArgs::parse();
let mut stdout = take_stdout()?;
let (set_builder_url, assessor_url) = if let Some(gateway) = args.ipfs_gateway {
(
format!("{}/ipfs/{}", gateway, args.set_builder_url.split('/').next_back().unwrap()),
format!("{}/ipfs/{}", gateway, args.assessor_url.split('/').next_back().unwrap()),
)
} else {
(args.set_builder_url, args.assessor_url)
};
let set_builder_program = fetch_url(set_builder_url, args.pinata_jwt.clone()).await?;
let assessor_program = fetch_url(assessor_url, args.pinata_jwt).await?;
let domain = eip712_domain(args.boundless_market_address, args.chain_id.try_into()?);
let prover = DefaultProver::new(
set_builder_program,
assessor_program,
args.prover_address,
domain.clone(),
)?;
let request = <ProofRequest>::abi_decode(&hex::decode(args.request.trim_start_matches("0x"))?)
.map_err(|_| anyhow::anyhow!("Failed to decode ProofRequest from input"))?;
let signature =
Signature::try_from(Bytes::from_hex(args.signature.trim_start_matches("0x"))?.as_ref())?;
let (fills, root_receipt, assessor_receipt) =
prover.fulfill(&[(request, signature.as_bytes().into())]).await?;
let order_fulfilled = OrderFulfilled::new(fills, root_receipt, assessor_receipt)?;
write!(&mut stdout, "{}", hex::encode(order_fulfilled.abi_encode()))
.context("failed to write to stdout")?;
stdout.flush().context("failed to flush stdout")?;
Ok(())
}
fn take_stdout() -> Result<File> {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle.flush()?;
let devnull = OpenOptions::new().write(true).open("/dev/null")?;
unsafe {
let dup_fd = libc::dup(handle.as_raw_fd());
ensure!(dup_fd >= 0, "call to libc::dup failed: {}", dup_fd);
let dup2_result = libc::dup2(devnull.as_raw_fd(), libc::STDOUT_FILENO);
ensure!(dup2_result >= 0, "call to libc::dup2 failed: {}", dup2_result);
Ok(File::from_raw_fd(dup_fd))
}
}