• 智能合约语言(eDSL)—— proc_macro实现合约init函数


            我们通过属性宏来实现合约的init函数,call函数其实和init是类似的;

            GitHub - XuHugo/xwasm

            构建属性宏,要在cargo.toml里面设置一些参数,这是必须的。一般来说,过程宏必须是一个库,或者作为工程的子库,不能单独作为一个源文件存在。

    1. [dependencies]
    2. syn = { version = "1.0.63", features = [ "full", "extra-traits" ] }
    3. quote = "=1.0.0"
    4. proc-macro2 = "1.0"
    5. [lib]
    6. proc-macro = true

    而编写过程宏,在stable版本里,我们需要借助三个crate:

    实现init的属性宏

            函数的签名如下,这个格式除了函数名字,其他的都不要修改;

    1. #[proc_macro_attribute]
    2. pub fn init(attr: TokenStream, item: TokenStream) -> TokenStream {

            需要加一个关键字proc_macro_attribute;由两个参数,一个attr,一个item;对应到合约里就是如下图;

            下一步解析attr,找到contract对应的合约名称放到contract变量中;

    1. let attrs = parse_macro_input!(attr as AttributeArgs);
    2. let contract = get_attribute(attrs.clone(), "contract").unwrap().unwrap();

            然后处理payable,之前没有讲解这个标记的作用,其实就是标记,合约函数是否可以接口币;

    1. let mut setup_function_args = proc_macro2::TokenStream::new();
    2. let mut function_args = vec![];
    3. let amount_ident = format_ident!("amount");
    4. if contains_attribute2(attrs.clone(), "payable") {
    5. function_args.push(quote!(#amount_ident));
    6. } else {
    7. setup_function_args.extend(quote! {
    8. if #amount_ident != 0 {
    9. return -1;
    10. }
    11. });
    12. };

            就是通过contains_attribute2判断是否包含,payable,如果有,就会将用户传入的代币的数量记录,否则就判断,用户传入代币数量不为零,就返回-1;

            然后就是结合合约名字,给函数重新命令,我们设定,init函数的名字为,init_合约名称,如果单单用这个做标识会有冲突,先不管这个;

    1. let ast = parse_macro_input!(item as Item);
    2. let init_function_name = format_ident!("init_{}", contract.value());
    3. let init_name = format!("init_{}", contract.value());

            现在开始处理item了,首先判断是否是函数;不是就返回错误了;

    1. let function_name = if let syn::Item::Fn(itemfn) = ast.clone() {
    2. itemfn.sig.ident
    3. } else {
    4. return syn::Error::new(Span::call_site(), format!("#[init] must be function."))
    5. .into_compile_error()
    6. .into();
    7. };

            最后重新组合init函数,

    1. let output = quote! {
    2. #ast
    3. #[export_name = #init_name]
    4. pub extern "C" fn #init_function_name(amount:u64)->i32{
    5. use xq_std::{ContractContext,serde_json};
    6. let initctx = ContractContext;
    7. #setup_function_args
    8. match #function_name(initctx, #(#function_args),*){
    9. Ok(o)=>{
    10. let r = serde_json::to_string(&o).unwrap();
    11. ContractContext.return_data(r.clone());
    12. return 1
    13. }
    14. Err(e)=>{
    15. let err = e.to_string();
    16. ContractContext.error(err.clone());
    17. return 0
    18. }
    19. }
    20. }
    21. };

            pub extern "C" fn 需要注意,虽然我们用rust写合约,但是写完之后,是编译成wasm格式的,相当于一个库文件了,最终谁会调用并不明确,所以需要使用这FFI的形式。

            这部分其实还是最终调用了我们item部分的函数,只是再调用之前添加一些检测和前置工作,例如payable的检测;调用之后呢,也添加了一些工作,例如,将返回值转化为json的字符串。有点类似与python的修饰符。

            call函数的实现其实和这个是一样,有细微的差别,大家可以自己去对比一下。

  • 相关阅读:
    专业138+总分400+南航南京航空航天大学878考研经验电子信息与通信工程,真题,大纲,参考书
    tls会话交互过程之一
    基于Java生活缴费系统设计实现(源码+lw+部署文档+讲解等)
    LeetCode中等题题解思路+源码合集(持续更新中)
    『忘了再学』Shell基础 — 13、环境变量(一)
    如何优雅的删除undo表空间
    pandas基础-pandas之Series+ 读取外部数据+dataframe+dataframe的索引
    设计模式(三)面向对象:贫血和充血模型下的MVC
    RHCSA之linux的简单使用
    主流报表开发工具有FastReport.NET V2022.3正式发布——支持SkiaSharp
  • 原文地址:https://blog.csdn.net/xq723310/article/details/136566410