The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hyperlane-monorepo/rust/utils/abigen/src/lib.rs

163 lines
5.0 KiB

use fuels_code_gen::ProgramType;
use std::collections::BTreeSet;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use inflector::Inflector;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum BuildType {
Ethers,
Fuels,
}
/// A `build.rs` tool for building a directory of ABIs. This will parse the
/// `abi_dir` for ABI files ending in `.json` and write the generated rust code
/// to `output_dir` and create an appropriate `mod.rs` file to.
pub fn generate_bindings_for_dir(
abi_dir: impl AsRef<Path>,
output_dir: impl AsRef<Path>,
build_type: BuildType,
) {
println!("cargo:rerun-if-changed={}", abi_dir.as_ref().display());
// clean old bindings
if let Err(e) = fs::remove_dir_all(&output_dir) {
println!("cargo:warning=Could not delete old bindings dir: {}", e);
};
fs::create_dir_all(&output_dir).expect("could not create bindings dir");
// generate bindings and get a list of the module names
let modules: BTreeSet<String> = fs::read_dir(abi_dir)
.expect("could not read ABI folder")
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| {
let ending_str = ".abi.json";
path.to_str()
.map(|p| p.ends_with(ending_str))
.unwrap_or_default()
})
.map(|contract_path| {
println!("Generating bindings for {:?}", &contract_path);
generate_bindings(&contract_path, &output_dir, build_type)
})
.collect();
// create mod.rs file
let mod_file_path = {
let mut p = output_dir.as_ref().to_path_buf();
p.push("mod.rs");
p
};
println!("Creating module file at {}", mod_file_path.display());
let mut mod_file = File::create(&mod_file_path).expect("could not create modfile");
writeln!(mod_file, "#![allow(warnings)]").unwrap();
Gelato relayer scaffolding (#950) * Revive fwd_req_op. * Transform iter map into a for loop in relayer.rs. * cargo fmt * Plumb signer into GelatoSubmitter. * Provide inbox signer to GelatoSubmitter init. This might fail, in which case the relayer can't really do anything for that inbox if it is configured to use Gelato. In that case, bail!(..). Bailing requires that the boot/init boilerplate in the relayer handle errors, so plumb/wrap those all in Result<> return types and update accordingly. * Plumb deps up to ForwardRequestArgs construction. * Plumb actual target chain, and rename chain --> target_chain. * Use 0xEE for native token always. * Plumb outbox chain id * Plumb address of IVM on target chain * Just use zero bytes instead of todo! to satisfy clippy etc. * Plumb IVM BaseContract into GelatoSubmitter so that we can use it to encode calldata for IVM.process(). * cargo fmt again * How to marshal bytes. * Add interface for accessing inbox contract eth address * Plumb inbox address into the gelato submitter finally. * cargo fmt * Actually return the address instead of todo!()... * Actually build the byte array of calldata! * Better error msg to fit < 100c * defer introducing fwd_req_op until next PR * Handful of pre-review cleanups: - Only give GelatoSubmitter the inbox domain instead of the whole InboxContracts - Don't wrap http: reqwest::Client in an Arc. It's unnecessary and handled internally for you, and better to clone() so that cxn pool reused. - Shorter names from imports where noticed - stub op.run() impl - shorter submitter field names where appropriate - dont pass GelatoConf to GelatoSubmitter - get correct visibility in abigen'd build code (and allow no doc comments to satisfy clippy) * rename data to call_data * cargo fmt * Defer field name change for chain_id and target in FwdReqArgs * Cleanup of relayer.rs including more clear data validation and imoprt scoping. * cargo fmt * Improve (?) address handling for inbox and ivm. * for InboxValidatorManagerVariants::contract_address(), actually delegate to the Mock variant implementation's contract_address() call instead of over-eagerly returning None. * Stub impl of _contract_address in mockall mock! Inbox contract impl. * for Other variants, match arm does unimplemented!(), so we avoid having to wrap in an Option, making cleaner at call sites * Minimize namespace qualifications where possible for Address-like types * attempt to make the IVM contract_address() for abacus-ethereum impl parallel to inbox w/r/t types * Move abacus domain conversion out of gelato::Chain and into private helper in gelato_submitter * More logical ordering of GelatoSubmitter struct fields * cargo fmt * Drop extra comment, clarify log * Plumb sponsor address from configs into GelatoSubmitter. Interpreted as a source chain address. * cargo fmt --all * stop trying to wrap http client in an arc, it's counterproductive * Write Ok once instead of every line * formatting * Do not separate with empty line the inner attributes found in lib.rs post-abigen-buildscript-exec Otherwise cargo fmt finds a diff and presubmits halt. * spelling typo * Change GelatoSubmitter construction from new to helper in relayer.rs. Also use simpler construction for tokenizing the proof for merkle trees when generating the forward request args struct. Was needlessly convoluted before using default trait unnecessarily etc. Use writeln instead of write in abigen util since clippy complains otherwise. * Get ivm_base_contract from ABI string directly rather than plumbing. * pass GelatoConf by as_ref() which refs inner type rather than &Option<GelatoConf> * Drop unrelated comment in gelato/chains.rs, noted elsewhere * Revive fwd_req_op. * Transform iter map into a for loop in relayer.rs. * cargo fmt * Plumb signer into GelatoSubmitter. * Provide inbox signer to GelatoSubmitter init. This might fail, in which case the relayer can't really do anything for that inbox if it is configured to use Gelato. In that case, bail!(..). Bailing requires that the boot/init boilerplate in the relayer handle errors, so plumb/wrap those all in Result<> return types and update accordingly. * Plumb deps up to ForwardRequestArgs construction. * Plumb actual target chain, and rename chain --> target_chain. * Use 0xEE for native token always. * Plumb outbox chain id * Plumb address of IVM on target chain * Just use zero bytes instead of todo! to satisfy clippy etc. * Plumb IVM BaseContract into GelatoSubmitter so that we can use it to encode calldata for IVM.process(). * cargo fmt again * How to marshal bytes. * Add interface for accessing inbox contract eth address * Plumb inbox address into the gelato submitter finally. * cargo fmt * Actually return the address instead of todo!()... * Actually build the byte array of calldata! * Better error msg to fit < 100c * defer introducing fwd_req_op until next PR * Handful of pre-review cleanups: - Only give GelatoSubmitter the inbox domain instead of the whole InboxContracts - Don't wrap http: reqwest::Client in an Arc. It's unnecessary and handled internally for you, and better to clone() so that cxn pool reused. - Shorter names from imports where noticed - stub op.run() impl - shorter submitter field names where appropriate - dont pass GelatoConf to GelatoSubmitter - get correct visibility in abigen'd build code (and allow no doc comments to satisfy clippy) * rename data to call_data * cargo fmt * Defer field name change for chain_id and target in FwdReqArgs * Cleanup of relayer.rs including more clear data validation and imoprt scoping. * cargo fmt * Improve (?) address handling for inbox and ivm. * for InboxValidatorManagerVariants::contract_address(), actually delegate to the Mock variant implementation's contract_address() call instead of over-eagerly returning None. * Stub impl of _contract_address in mockall mock! Inbox contract impl. * for Other variants, match arm does unimplemented!(), so we avoid having to wrap in an Option, making cleaner at call sites * Minimize namespace qualifications where possible for Address-like types * attempt to make the IVM contract_address() for abacus-ethereum impl parallel to inbox w/r/t types * Move abacus domain conversion out of gelato::Chain and into private helper in gelato_submitter * More logical ordering of GelatoSubmitter struct fields * cargo fmt * Drop extra comment, clarify log * Plumb sponsor address from configs into GelatoSubmitter. Interpreted as a source chain address. * cargo fmt --all * stop trying to wrap http client in an arc, it's counterproductive * Write Ok once instead of every line * formatting * Do not separate with empty line the inner attributes found in lib.rs post-abigen-buildscript-exec Otherwise cargo fmt finds a diff and presubmits halt. * spelling typo * Change GelatoSubmitter construction from new to helper in relayer.rs. Also use simpler construction for tokenizing the proof for merkle trees when generating the forward request args struct. Was needlessly convoluted before using default trait unnecessarily etc. Use writeln instead of write in abigen util since clippy complains otherwise. * Get ivm_base_contract from ABI string directly rather than plumbing. * pass GelatoConf by as_ref() which refs inner type rather than &Option<GelatoConf> * Drop unrelated comment in gelato/chains.rs, noted elsewhere * Wrap Inbox::contract_address() result in an Option. * Don't over-zealously mark pub(crate) struct's members pub(crate). pub is good enough. * More natural iter map construction in Token byte packing for call_data on process. * More natural iter map construction in Token byte packing for call_data on process. * Restore fwd req op struct and fmt * rm unnecessary Results * add with_capacity * Make contract_address return Address, not Option<Address> * rm unimplemeneted * Rm address field of validator manager * Mid refactor of creating GelatoSubmitter * add inbox_gelato_chain * cleaning * Some shuffling around * some moving around * Finishing touches * Remove unnecessary type definition & some unused code Co-authored-by: webbhorn <webb.horn@gmail.com>
2 years ago
writeln!(mod_file, "#![allow(clippy::all)]").unwrap();
write!(mod_file, "#![allow(missing_docs)]\n\n").unwrap();
for m in modules {
writeln!(mod_file, "pub(crate) mod {};", m).expect("failed to write to modfile");
}
drop(mod_file);
}
/// Generate the bindings for a given ABI and return the new module name. Will
/// create a file within the designated path with the correct `{module_name}.rs`
/// format.
pub fn generate_bindings(
contract_path: impl AsRef<Path>,
output_dir: impl AsRef<Path>,
build_type: BuildType,
) -> String {
println!("path {:?}", contract_path.as_ref().display());
// contract name is the first
let contract_name = contract_path
.as_ref()
.file_name()
.and_then(OsStr::to_str)
.expect("contract filename not is not valid unicode.")
.split('.')
.next()
.expect("missing extension in path");
let module_name = contract_name.to_snake_case();
let output_file = {
let mut p = output_dir.as_ref().to_path_buf();
p.push(format!("{module_name}.rs"));
p
};
let abi_source = contract_path.as_ref().to_str().expect("valid utf8 path");
#[cfg(feature = "ethers")]
if build_type == BuildType::Ethers {
ethers::contract::Abigen::new(contract_name, abi_source)
.expect("could not instantiate Abigen")
.generate()
.expect("could not generate bindings")
.write_to_file(&output_file)
.expect("Could not write bindings to file");
}
#[cfg(feature = "fuels")]
if build_type == BuildType::Fuels {
let tokens = fuels_code_gen::Abigen::generate(
vec![fuels_code_gen::AbigenTarget {
name: contract_name.into(),
abi: abi_source.into(),
program_type: ProgramType::Contract,
}],
false,
)
.expect("could not generate bindings")
.to_string();
let mut outfile = File::create(&output_file).expect("Could not open output file");
outfile
.write_all(tokens.as_bytes())
.expect("Could not write bindings to file");
fmt_file(&output_file);
}
module_name
}
#[cfg(feature = "fmt")]
fn fmt_file(path: &Path) {
if let Err(err) = std::process::Command::new(rustfmt_path())
.args(["--edition", "2021"])
.arg(path)
.output()
{
println!("cargo:warning=Failed to run rustfmt for {path:?}, ignoring ({err})");
}
}
/// Get the rustfmt binary path.
#[cfg(feature = "fmt")]
fn rustfmt_path() -> &'static Path {
use std::path::PathBuf;
// lazy static var
static mut PATH: Option<PathBuf> = None;
if let Some(path) = unsafe { PATH.as_ref() } {
return path;
}
if let Ok(path) = std::env::var("RUSTFMT") {
unsafe {
PATH = Some(PathBuf::from(path));
PATH.as_ref().unwrap()
}
} else {
// assume it is in PATH
unsafe {
PATH = Some(which::which("rustmft").unwrap_or_else(|_| "rustfmt".into()));
PATH.as_ref().unwrap()
}
}
}