use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{LitStr, parse_macro_input, spanned::Spanned};
mod abi;
mod codegen;
mod parser;
use abi::MoveModuleABI;
use codegen::generate_contract_impl;
#[proc_macro]
pub fn aptos_contract(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as parser::ContractInput);
let abi: MoveModuleABI = match serde_json::from_str(&input.abi) {
Ok(abi) => abi,
Err(e) => {
return syn::Error::new(input.name.span(), format!("Failed to parse ABI JSON: {e}"))
.to_compile_error()
.into();
}
};
let source_info = input.source.as_ref().map(|s| parser::parse_move_source(s));
let tokens = generate_contract_impl(&input.name, &abi, source_info.as_ref());
tokens.into()
}
#[proc_macro]
pub fn aptos_contract_file(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as parser::FileInput);
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
let manifest_path = std::path::Path::new(&manifest_dir);
let file_path = manifest_path.join(&input.path);
let canonical_manifest = match manifest_path.canonicalize() {
Ok(p) => p,
Err(e) => {
return syn::Error::new(
input.name.span(),
format!("Failed to resolve project directory: {e}"),
)
.to_compile_error()
.into();
}
};
let canonical_file = match file_path.canonicalize() {
Ok(p) => p,
Err(e) => {
return syn::Error::new(
input.name.span(),
format!("Failed to resolve ABI file path '{}': {e}", input.path),
)
.to_compile_error()
.into();
}
};
if !canonical_file.starts_with(&canonical_manifest) {
return syn::Error::new(
input.name.span(),
format!(
"ABI file path '{}' resolves outside the project directory",
input.path
),
)
.to_compile_error()
.into();
}
let abi_content = match std::fs::read_to_string(&file_path) {
Ok(content) => content,
Err(e) => {
return syn::Error::new(
input.name.span(),
format!("Failed to read ABI file '{}': {e}", file_path.display()),
)
.to_compile_error()
.into();
}
};
let abi: MoveModuleABI = match serde_json::from_str(&abi_content) {
Ok(abi) => abi,
Err(e) => {
return syn::Error::new(
input.name.span(),
format!(
"Failed to parse ABI JSON from '{}': {e}",
file_path.display(),
),
)
.to_compile_error()
.into();
}
};
let source_info = if let Some(source_path) = input.source_path.as_ref() {
let source_file = std::path::Path::new(&manifest_dir).join(source_path);
match std::fs::read_to_string(&source_file) {
Ok(content) => Some(parser::parse_move_source(&content)),
Err(e) => {
return syn::Error::new(
input.name.span(),
format!(
"Failed to read Move source file '{}': {e}",
source_file.display(),
),
)
.to_compile_error()
.into();
}
}
} else {
None
};
let tokens = generate_contract_impl(&input.name, &abi, source_info.as_ref());
tokens.into()
}
#[proc_macro_derive(MoveStruct, attributes(move_struct))]
pub fn derive_move_struct(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let name = &input.ident;
let mut address = None;
let mut module = None;
let mut struct_name = None;
let mut parse_error: Option<syn::Error> = None;
for attr in &input.attrs {
if attr.path().is_ident("move_struct") {
let result = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("address") {
let value: LitStr = meta.value()?.parse()?;
address = Some(value.value());
} else if meta.path.is_ident("module") {
let value: LitStr = meta.value()?.parse()?;
module = Some(value.value());
} else if meta.path.is_ident("name") {
let value: LitStr = meta.value()?.parse()?;
struct_name = Some(value.value());
} else {
return Err(syn::Error::new(
meta.path.span(),
format!(
"Unknown attribute '{}'. Expected 'address', 'module', or 'name'",
meta.path
.get_ident()
.map_or_else(|| "?".to_string(), ToString::to_string)
),
));
}
Ok(())
});
if let Err(e) = result {
parse_error = Some(e);
break;
}
}
}
if let Some(e) = parse_error {
return e.to_compile_error().into();
}
let address = address.unwrap_or_else(|| "0x1".to_string());
let module = module.unwrap_or_else(|| "unknown".to_string());
let struct_name = struct_name.unwrap_or_else(|| name.to_string());
let type_tag = format!("{address}::{module}::{struct_name}");
let type_tag_lit = LitStr::new(&type_tag, Span::call_site());
let expanded = quote! {
impl #name {
pub fn type_tag() -> &'static str {
#type_tag_lit
}
pub fn to_bcs(&self) -> ::aptos_sdk::error::AptosResult<Vec<u8>> {
::aptos_sdk::aptos_bcs::to_bytes(self)
.map_err(|e| ::aptos_sdk::error::AptosError::Bcs(e.to_string()))
}
pub fn from_bcs(bytes: &[u8]) -> ::aptos_sdk::error::AptosResult<Self> {
::aptos_sdk::aptos_bcs::from_bytes(bytes)
.map_err(|e| ::aptos_sdk::error::AptosError::Bcs(e.to_string()))
}
}
};
expanded.into()
}