odra-codegen 0.7.1

Code generators for Odra IR.
Documentation
use odra_ir::module::{Constructor, Method};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::ReturnType;

use crate::generator::module_impl::deploy::common;

use super::{args_to_arg_names_stream, args_to_fn_args, args_to_runtime_args_stream};

pub fn generate_code(
    struct_ident: &Ident,
    deployer_ident: &Ident,
    ref_ident: &Ident,
    constructors: &[&Constructor],
    methods: &[&Method]
) -> TokenStream {
    let entrypoint_calls = build_entrypoints_calls(methods, struct_ident);
    let constructor_calls = build_constructor_calls(constructors, struct_ident);
    let constructors = build_constructors(
        constructors,
        entrypoint_calls,
        constructor_calls,
        struct_ident,
        ref_ident
    );

    quote! {
        impl #deployer_ident {
            #constructors
        }
    }
}

fn build_constructors(
    constructors: &[&Constructor],
    entrypoints_stream: TokenStream,
    constructors_stream: TokenStream,
    struct_ident: &Ident,
    ref_ident: &Ident
) -> TokenStream {
    if constructors.is_empty() {
        build_default_constructor(entrypoints_stream, constructors_stream, ref_ident)
    } else {
        constructors
            .iter()
            .map(|constructor| {
                build_constructor(
                    constructor,
                    entrypoints_stream.clone(),
                    constructors_stream.clone(),
                    struct_ident,
                    ref_ident
                )
            })
            .collect::<TokenStream>()
    }
}

fn build_constructor(
    constructor: &Constructor,
    entrypoints_stream: TokenStream,
    constructors_stream: TokenStream,
    struct_ident: &Ident,
    ref_ident: &Ident
) -> TokenStream {
    let constructor_ident = &constructor.ident;

    let fn_sig = common::constructor_sig(constructor, ref_ident);

    let args = args_to_runtime_args_stream(&constructor.args);
    let fn_args = args_to_fn_args(&constructor.args);

    quote! {
        pub #fn_sig {
            use odra::types::casper_types::RuntimeArgs;

            let mut entrypoints = odra::prelude::collections::BTreeMap::<
                odra::prelude::string::String,
                (odra::prelude::vec::Vec<odra::prelude::string::String>, fn(odra::prelude::string::String, &RuntimeArgs) -> odra::prelude::vec::Vec<u8>)
            >::new();
            #entrypoints_stream

            let mut constructors = odra::prelude::collections::BTreeMap::<
                odra::prelude::string::String,
                (odra::prelude::vec::Vec<odra::prelude::string::String>, fn(odra::prelude::string::String, &RuntimeArgs) -> odra::prelude::vec::Vec<u8>)
            >::new();
            #constructors_stream

            let args = {
                #args
            };
            let constructor: Option<(
                odra::prelude::string::String, &RuntimeArgs,
                fn(odra::prelude::string::String, &RuntimeArgs) -> odra::prelude::vec::Vec<u8>)
            > = Some((
                odra::prelude::string::String::from(stringify!(#constructor_ident)),
                &args,
                |name, args| {
                    let keys = <#struct_ident as odra::types::contract_def::Node>::__keys();
                    let keys = keys
                        .iter()
                        .map(odra::prelude::string::String::as_str)
                        .collect::<odra::prelude::vec::Vec<_>>();
                    let (mut instance, _) = <#struct_ident as odra::StaticInstance>::instance(keys.as_slice());
                    instance.#constructor_ident( #fn_args );
                    odra::prelude::vec::Vec::new()
                }
            ));
            let address = odra::test_env::register_contract(constructor, constructors, entrypoints);
            #ref_ident::at(&address)
        }
    }
}

fn build_default_constructor(
    entrypoints_stream: TokenStream,
    constructors_stream: TokenStream,
    ref_ident: &Ident
) -> TokenStream {
    quote! {
        pub fn default() -> #ref_ident {
            use odra::types::casper_types::RuntimeArgs;

            let mut entrypoints = odra::prelude::collections::BTreeMap::<
                odra::prelude::string::String,
                (odra::prelude::vec::Vec<odra::prelude::string::String>, fn(odra::prelude::string::String, &RuntimeArgs) -> odra::prelude::vec::Vec<u8>)
            >::new();
            #entrypoints_stream

            let mut constructors = odra::prelude::collections::BTreeMap::<
                odra::prelude::string::String,
                (odra::prelude::vec::Vec<odra::prelude::string::String>, fn(odra::prelude::string::String, &RuntimeArgs) -> odra::prelude::vec::Vec<u8>)
            >::new();
            #constructors_stream

            let address = odra::test_env::register_contract(None, constructors, entrypoints);
            #ref_ident::at(&address)
        }
    }
}

fn build_entrypoints_calls(methods: &[&Method], struct_ident: &Ident) -> TokenStream {
    methods
        .iter()
        .map(|entrypoint| {
            let ident = &entrypoint.ident;
            let name = quote!(odra::prelude::string::String::from(stringify!(#ident)));
            let arg_names = args_to_arg_names_stream(&entrypoint.args);
            let return_value = return_value(entrypoint);
            let args = args_to_fn_args(&entrypoint.args);
            let attached_value_check = attached_value(entrypoint);
            let (reentrancy_check, reentrancy_cleanup) = reentrancy_code(entrypoint);

            quote! {
                entrypoints.insert(#name, (#arg_names, |name, args| {
                    #reentrancy_check
                    #attached_value_check
                    let keys = <#struct_ident as odra::types::contract_def::Node>::__keys();
                    let keys = keys
                        .iter()
                        .map(odra::prelude::string::String::as_str)
                        .collect::<odra::prelude::vec::Vec<_>>();
                    let (mut instance, _) = <#struct_ident as odra::StaticInstance>::instance(keys.as_slice());
                    let result = instance.#ident(#args);
                    #reentrancy_cleanup
                    #return_value
                }));
            }
        })
        .collect::<TokenStream>()
}

fn build_constructor_calls(constructors: &[&Constructor], struct_ident: &Ident) -> TokenStream {
    constructors
        .iter()
        .map(|constructor| {
            let ident = &constructor.ident;
            let args = args_to_fn_args(&constructor.args);
            let arg_names = args_to_arg_names_stream(&constructor.args);

            quote! {
                constructors.insert(odra::prelude::string::String::from(stringify!(#ident)), (#arg_names,
                    |name, args| {
                        let keys = <#struct_ident as odra::types::contract_def::Node>::__keys();
                        let keys = keys
                            .iter()
                            .map(odra::prelude::string::String::as_str)
                            .collect::<odra::prelude::vec::Vec<_>>();
                        let (mut instance, _) = <#struct_ident as odra::StaticInstance>::instance(keys.as_slice());
                        instance.#ident( #args );
                        odra::prelude::vec::Vec::new()
                    }
                ));
            }
        })
        .collect::<TokenStream>()
}

fn reentrancy_code(entrypoint: &Method) -> (Option<TokenStream>, Option<TokenStream>) {
    let non_reentrant = entrypoint.attrs.iter().any(|a| a.is_non_reentrant());
    let reentrancy_check = non_reentrant.then(|| {
        quote! {
            if odra::contract_env::get_var(b"__reentrancy_guard").unwrap_or_default(){
                odra::contract_env::revert(odra::types::ExecutionError::reentrant_call());
            }
            odra::contract_env::set_var(b"__reentrancy_guard", true);
        }
    });
    let reentrancy_cleanup =
        non_reentrant.then(|| quote!(odra::contract_env::set_var(b"__reentrancy_guard", false);));
    (reentrancy_check, reentrancy_cleanup)
}

fn attached_value(entrypoint: &Method) -> TokenStream {
    match entrypoint.is_payable() {
        true => quote!(),
        false => quote! {
            if odra::contract_env::attached_value() > odra::types::casper_types::U512::zero() {
                odra::contract_env::revert(odra::types::ExecutionError::non_payable());
            }
        }
    }
}

fn return_value(entrypoint: &Method) -> TokenStream {
    match &entrypoint.ret {
        ReturnType::Default => quote!(odra::prelude::vec::Vec::new()),
        ReturnType::Type(_, _) => {
            quote!(odra::types::casper_types::bytesrepr::ToBytes::into_bytes(result).unwrap())
        }
    }
}