1use proc_macro::TokenStream;
25use proc_macro2::Span;
26use quote::quote;
27use syn::{LitStr, parse_macro_input, spanned::Spanned};
28
29mod abi;
30mod codegen;
31mod parser;
32
33use abi::MoveModuleABI;
34use codegen::generate_contract_impl;
35
36#[proc_macro]
78pub fn aptos_contract(input: TokenStream) -> TokenStream {
79 let input = parse_macro_input!(input as parser::ContractInput);
80
81 let abi: MoveModuleABI = match serde_json::from_str(&input.abi) {
83 Ok(abi) => abi,
84 Err(e) => {
85 return syn::Error::new(input.name.span(), format!("Failed to parse ABI JSON: {e}"))
86 .to_compile_error()
87 .into();
88 }
89 };
90
91 let source_info = input.source.as_ref().map(|s| parser::parse_move_source(s));
93
94 let tokens = generate_contract_impl(&input.name, &abi, source_info.as_ref());
96
97 tokens.into()
98}
99
100#[proc_macro]
110pub fn aptos_contract_file(input: TokenStream) -> TokenStream {
111 let input = parse_macro_input!(input as parser::FileInput);
112
113 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
115 let manifest_path = std::path::Path::new(&manifest_dir);
116 let file_path = manifest_path.join(&input.path);
117
118 let canonical_manifest = match manifest_path.canonicalize() {
123 Ok(p) => p,
124 Err(e) => {
125 return syn::Error::new(
126 input.name.span(),
127 format!("Failed to resolve project directory: {e}"),
128 )
129 .to_compile_error()
130 .into();
131 }
132 };
133 let canonical_file = match file_path.canonicalize() {
134 Ok(p) => p,
135 Err(e) => {
136 return syn::Error::new(
137 input.name.span(),
138 format!("Failed to resolve ABI file path '{}': {e}", input.path),
139 )
140 .to_compile_error()
141 .into();
142 }
143 };
144 if !canonical_file.starts_with(&canonical_manifest) {
145 return syn::Error::new(
146 input.name.span(),
147 format!(
148 "ABI file path '{}' resolves outside the project directory",
149 input.path
150 ),
151 )
152 .to_compile_error()
153 .into();
154 }
155
156 let abi_content = match std::fs::read_to_string(&file_path) {
157 Ok(content) => content,
158 Err(e) => {
159 return syn::Error::new(
161 input.name.span(),
162 format!("Failed to read ABI file '{}': {e}", file_path.display()),
163 )
164 .to_compile_error()
165 .into();
166 }
167 };
168
169 let abi: MoveModuleABI = match serde_json::from_str(&abi_content) {
170 Ok(abi) => abi,
171 Err(e) => {
172 return syn::Error::new(
173 input.name.span(),
174 format!(
175 "Failed to parse ABI JSON from '{}': {e}",
176 file_path.display(),
177 ),
178 )
179 .to_compile_error()
180 .into();
181 }
182 };
183
184 let source_info = if let Some(source_path) = input.source_path.as_ref() {
186 let source_file = std::path::Path::new(&manifest_dir).join(source_path);
187 match std::fs::read_to_string(&source_file) {
188 Ok(content) => Some(parser::parse_move_source(&content)),
189 Err(e) => {
190 return syn::Error::new(
191 input.name.span(),
192 format!(
193 "Failed to read Move source file '{}': {e}",
194 source_file.display(),
195 ),
196 )
197 .to_compile_error()
198 .into();
199 }
200 }
201 } else {
202 None
203 };
204
205 let tokens = generate_contract_impl(&input.name, &abi, source_info.as_ref());
206
207 tokens.into()
208}
209
210#[proc_macro_derive(MoveStruct, attributes(move_struct))]
228pub fn derive_move_struct(input: TokenStream) -> TokenStream {
229 let input = parse_macro_input!(input as syn::DeriveInput);
230
231 let name = &input.ident;
232
233 let mut address = None;
235 let mut module = None;
236 let mut struct_name = None;
237 let mut parse_error: Option<syn::Error> = None;
238
239 for attr in &input.attrs {
240 if attr.path().is_ident("move_struct") {
241 let result = attr.parse_nested_meta(|meta| {
242 if meta.path.is_ident("address") {
243 let value: LitStr = meta.value()?.parse()?;
244 address = Some(value.value());
245 } else if meta.path.is_ident("module") {
246 let value: LitStr = meta.value()?.parse()?;
247 module = Some(value.value());
248 } else if meta.path.is_ident("name") {
249 let value: LitStr = meta.value()?.parse()?;
250 struct_name = Some(value.value());
251 } else {
252 return Err(syn::Error::new(
253 meta.path.span(),
254 format!(
255 "Unknown attribute '{}'. Expected 'address', 'module', or 'name'",
256 meta.path
257 .get_ident()
258 .map_or_else(|| "?".to_string(), ToString::to_string)
259 ),
260 ));
261 }
262 Ok(())
263 });
264
265 if let Err(e) = result {
266 parse_error = Some(e);
267 break;
268 }
269 }
270 }
271
272 if let Some(e) = parse_error {
274 return e.to_compile_error().into();
275 }
276
277 let address = address.unwrap_or_else(|| "0x1".to_string());
278 let module = module.unwrap_or_else(|| "unknown".to_string());
279 let struct_name = struct_name.unwrap_or_else(|| name.to_string());
280
281 let type_tag = format!("{address}::{module}::{struct_name}");
282 let type_tag_lit = LitStr::new(&type_tag, Span::call_site());
284
285 let expanded = quote! {
286 impl #name {
287 pub fn type_tag() -> &'static str {
289 #type_tag_lit
290 }
291
292 pub fn to_bcs(&self) -> ::aptos_sdk::error::AptosResult<Vec<u8>> {
294 ::aptos_sdk::aptos_bcs::to_bytes(self)
295 .map_err(|e| ::aptos_sdk::error::AptosError::Bcs(e.to_string()))
296 }
297
298 pub fn from_bcs(bytes: &[u8]) -> ::aptos_sdk::error::AptosResult<Self> {
300 ::aptos_sdk::aptos_bcs::from_bytes(bytes)
301 .map_err(|e| ::aptos_sdk::error::AptosError::Bcs(e.to_string()))
302 }
303 }
304 };
305
306 expanded.into()
307}