vialabs-stellar-macros 0.1.9

Proc macro for providing default implementations of traits for Stellar contracts in the VIA cross-chain messaging system
Documentation
//! # VIA Labs Stellar Macros
//!
//! Procedural macros for generating default method implementations in Stellar contracts.
//!
//! This crate provides the `#[default_impl]` attribute macro that automatically generates
//! default implementations for trait methods, reducing boilerplate when implementing
//! vialabs-stellar-common like `MessageClientV4Interface`.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemImpl};

fn get_default_methods(trait_name: &str) -> Vec<syn::ImplItem> {
  match trait_name {
    "MessageClientV4Interface" => vec![
      syn::parse_quote! {
        fn only_message_owner(env: &soroban_sdk::Env) {
          vialabs_stellar_common::message_client_v4::Base::only_message_owner(env)
        }
      },
      syn::parse_quote! {
        fn set_message_owner(env: &soroban_sdk::Env, owner: soroban_sdk::Address) {
          vialabs_stellar_common::message_client_v4::Base::set_message_owner(env, owner)
        }
      },
      syn::parse_quote! {
        fn get_message_owner(env: &soroban_sdk::Env) -> soroban_sdk::Address {
          vialabs_stellar_common::message_client_v4::Base::get_message_owner(env)
        }
      },
      syn::parse_quote! {
        fn set_message_gateway(env: &soroban_sdk::Env, contract_address: soroban_sdk::Address) {
          vialabs_stellar_common::message_client_v4::Base::set_message_gateway(env, contract_address)
        }
      },
      syn::parse_quote! {
        fn get_message_gateway(env: &soroban_sdk::Env) -> soroban_sdk::Address {
          vialabs_stellar_common::message_client_v4::Base::get_message_gateway(env)
        }
      },
      syn::parse_quote! {
        fn set_message_endpoints(env: &soroban_sdk::Env, chains: soroban_sdk::Vec<u64>, endpoints: soroban_sdk::Vec<soroban_sdk::Bytes>) {
          vialabs_stellar_common::message_client_v4::Base::set_message_endpoints(env, chains, endpoints)
        }
      },
      syn::parse_quote! {
        fn get_endpoint_by_chain_id(env: &soroban_sdk::Env, chain_id: u64) -> soroban_sdk::Bytes {
          vialabs_stellar_common::message_client_v4::Base::get_endpoint_by_chain_id(env, chain_id)
        }
      },
      syn::parse_quote! {
        fn message_send(env: &soroban_sdk::Env, destination_chain: u64, chain_data: soroban_sdk::Bytes, confirmations: u32) -> u128 {
          vialabs_stellar_common::message_client_v4::Base::message_send(env, destination_chain, chain_data, confirmations)
        }
      },
      syn::parse_quote! {
        fn validate_chain_sender(env: &soroban_sdk::Env, source_chain_id: u64, sender: soroban_sdk::Bytes) {
          vialabs_stellar_common::message_client_v4::Base::validate_chain_sender(env, source_chain_id, sender)
        }
      },
      syn::parse_quote! {
        fn message_process_from_gateway(env: &soroban_sdk::Env, message: vialabs_stellar_common::message_client_v4::ProcessFromGatewayRequest) {
          vialabs_stellar_common::message_client_v4::Base::validate_chain_sender(env, message.source_chain_id.clone(), message.sender.clone());
          vialabs_stellar_common::message_client_v4::Base::message_process_from_gateway(env, message)
        }
      },
    ],

    not_supported => {
      panic!("Trait {not_supported} is not supported by #[default_impl]")
    }
  }
}

fn generate_default_impl(item: TokenStream) -> TokenStream {
  let input = parse_macro_input!(item as ItemImpl);

  let trait_name = match &input.trait_ {
    Some((_, path, _)) => path.segments.last().unwrap().ident.to_string(),
    None => panic!("#[default_impl] must be used on a trait implementation"),
  };

  let mut user_methods = std::collections::HashSet::new();

  for item in &input.items {
    if let syn::ImplItem::Fn(method) = item {
      user_methods.insert(method.sig.ident.to_string());
    }
  }

  let mut default_methods = get_default_methods(&trait_name);

  default_methods.retain(|item| {
    if let syn::ImplItem::Fn(method) = item {
      !user_methods.contains(&method.sig.ident.to_string())
    } else {
      true
    }
  });

  let mut existing_items = input.items.clone();

  existing_items.extend(default_methods);

  let new_impl = ItemImpl {
    items: existing_items,
    ..input
  };

  let expanded = {
    quote! { #new_impl }
  };

  TokenStream::from(quote! { #expanded })
}

/// Automatically generates default method implementations for trait methods.
///
/// Returns a vector of default method implementations that can be automatically
/// included when implementing a trait.
///
/// # Supported Traits
///
/// Currently supports `MessageClientV4Interface`. Default implementations are provided for:
/// - `only_message_owner`, `set_message_owner`, `get_message_owner`
/// - `set_message_gateway`, `get_message_gateway`
/// - `set_message_endpoints`, `get_endpoint_by_chain_id`
/// - `message_send`, `validate_chain_sender`, `message_process_from_gateway`
///
/// Note: `message_process` must be implemented by the user and is not provided by default.
///
/// # Arguments
///
/// This macro does not accept any arguments. It must be applied to a trait implementation.
///
/// # Panics
///
/// Panics if:
/// - The macro is used on something other than a trait implementation
/// - An unsupported trait is provided
#[proc_macro_attribute]
pub fn default_impl(attrs: TokenStream, item: TokenStream) -> TokenStream {
  assert!(attrs.is_empty(), "This macro does not accept any arguments");

  generate_default_impl(item)
}