Skip to content

Commit

Permalink
Generate odra::contract_def::HasEntrypoint
Browse files Browse the repository at this point in the history
  • Loading branch information
kpob committed Dec 6, 2023
1 parent 7fe2d43 commit cdf2818
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 22 deletions.
29 changes: 10 additions & 19 deletions core/src/contract_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pub struct Entrypoint {
pub args: Vec<Argument>,
pub is_mut: bool,
pub ret: CLType,
pub ty: EntrypointType
pub ty: EntrypointType,
pub attributes: Vec<EntrypointAttribute>
}

/// Defines an argument passed to an entrypoint.
Expand All @@ -26,16 +27,6 @@ pub struct Argument {
pub is_slice: bool
}

impl EntrypointType {
pub fn is_non_reentrant(&self) -> bool {
match self {
EntrypointType::Constructor { non_reentrant } => *non_reentrant,
EntrypointType::Public { non_reentrant } => *non_reentrant,
EntrypointType::PublicPayable { non_reentrant } => *non_reentrant
}
}
}

/// Defines an event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Event {
Expand All @@ -53,11 +44,15 @@ impl Event {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum EntrypointType {
/// A special entrypoint that can be called just once on the contract initialization.
Constructor { non_reentrant: bool },
Constructor,
/// A regular entrypoint.
Public { non_reentrant: bool },
/// A payable entrypoint.
PublicPayable { non_reentrant: bool }
Public
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum EntrypointAttribute {
NonReentrant,
Payable
}

/// A trait that should be implemented by each smart contract to allow the backend
Expand All @@ -67,10 +62,6 @@ pub trait HasEntrypoints {
fn entrypoints() -> Vec<Entrypoint>;
}

/// A trait that should be implemented by each smart contract to allow the backend.
pub trait HasIdent {
fn ident() -> String;
}
/// A trait that should be implemented by each smart contract to allow the backend.
pub trait HasEvents {
fn events() -> Vec<Event>;
Expand Down
7 changes: 4 additions & 3 deletions odra-macros/src/ast/blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ impl TryFrom<&'_ ModuleIR> for SchemaFnItem {

let module_name = module.module_str()?;
let expr_events = utils::expr::events(&ty_module);
let expr_entrypoints = utils::expr::entrypoints(&ty_module);

let expr = parse_quote!(odra::contract_def::ContractBlueprint {
let expr = parse_quote!(#ty_blueprint {
#ident_name: #module_name,
#ident_events: #expr_events,
#ident_entrypoints: vec![]
#ident_entrypoints: #expr_entrypoints
});

Ok(Self {
Expand Down Expand Up @@ -94,7 +95,7 @@ mod test {
odra::contract_def::ContractBlueprint {
name: "Erc20",
events: <Erc20 as odra::contract_def::HasEvents>::events(),
entrypoints: vec![]
entrypoints: <Erc20 as odra::contract_def::HasEntrypoints>::entrypoints()
}
}
}
Expand Down
193 changes: 193 additions & 0 deletions odra-macros/src/ast/entrypoints_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
use syn::parse_quote;

use crate::ir::FnIR;
use crate::{ir::ModuleIR, utils};

#[derive(syn_derive::ToTokens)]
pub struct HasEntrypointsImplItem {
impl_token: syn::token::Impl,
has_ident_ty: syn::Type,
for_token: syn::token::For,
module_ident: syn::Ident,
#[syn(braced)]
brace_token: syn::token::Brace,
#[syn(in = brace_token)]
events_fn: EntrypointsFnItem
}

impl TryFrom<&'_ ModuleIR> for HasEntrypointsImplItem {
type Error = syn::Error;

fn try_from(struct_ir: &'_ ModuleIR) -> Result<Self, Self::Error> {
Ok(Self {
impl_token: Default::default(),
has_ident_ty: utils::ty::has_entrypoints(),
for_token: Default::default(),
module_ident: struct_ir.module_ident()?,
brace_token: Default::default(),
events_fn: struct_ir.try_into()?
})
}
}

#[derive(syn_derive::ToTokens)]
pub struct EntrypointsFnItem {
sig: syn::Signature,
#[syn(braced)]
brace_token: syn::token::Brace,
#[syn(in = brace_token)]
expr: syn::Expr
}

impl TryFrom<&'_ ModuleIR> for EntrypointsFnItem {
type Error = syn::Error;

fn try_from(struct_ir: &'_ ModuleIR) -> Result<Self, Self::Error> {
let ident_entrypoints = utils::ident::entrypoints();
let entrypoint_ty = utils::ty::entry_point_def();
let expr = struct_entrypoints_expr(struct_ir)?;

Ok(Self {
sig: parse_quote!(fn #ident_entrypoints() -> Vec<#entrypoint_ty>),
brace_token: Default::default(),
expr
})
}
}

fn struct_entrypoints_expr(ir: &ModuleIR) -> Result<syn::Expr, syn::Error> {
let struct_entrypoints = ir
.functions()
.iter()
.map(|f| {
let ident = f.name_str();
let args = entrypoint_args(f)?;
let is_mut = f.is_mut();
let ret = match f.return_type() {
syn::ReturnType::Default => utils::expr::unit_cl_type(),
syn::ReturnType::Type(_, ty) => utils::expr::as_cl_type(&ty)
};
let ty = f
.is_constructor()
.then(utils::ty::entry_point_def_ty_constructor)
.unwrap_or_else(utils::ty::entry_point_def_ty_public);
let is_payable_attr = f.is_payable().then(utils::ty::entry_point_def_attr_payable);
let is_non_reentrant = f
.is_non_reentrant()
.then(utils::ty::entry_point_def_attr_non_reentrant);
let attributes = vec![is_payable_attr, is_non_reentrant]
.into_iter()
.flatten()
.collect::<Vec<_>>();
let ty_entrypoint = utils::ty::entry_point_def();

let expr: syn::Expr = parse_quote!(#ty_entrypoint {
ident: String::from(#ident),
args: #args,
is_mut: #is_mut,
ret: #ret,
ty: #ty,
attributes: vec![#(#attributes),*]
});
Ok(expr)
})
.collect::<Result<syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>, syn::Error>>(
)?;
Ok(parse_quote!(vec![#struct_entrypoints]))
}

fn entrypoint_args(f: &FnIR) -> Result<syn::Expr, syn::Error> {
let args = f
.named_args()
.iter()
.map(|arg| {
let ty_arg = utils::ty::entry_point_def_arg();
let ident = arg.name_str()?;
let ty = utils::expr::as_cl_type(&arg.ty()?);

let expr: syn::Expr = parse_quote!(#ty_arg {
ident: String::from(#ident),
ty: #ty,
is_ref: false,
is_slice: false
});
Ok(expr)
})
.collect::<Result<Vec<syn::Expr>, syn::Error>>()?;
Ok(parse_quote!(vec![#(#args),*]))
}

#[cfg(test)]
mod test {
use crate::test_utils;
use quote::quote;

use super::HasEntrypointsImplItem;

#[test]
fn test_entrypoints() {
let module = test_utils::mock_module();
let expected = quote!(
impl odra::contract_def::HasEntrypoints for Erc20 {
fn entrypoints() -> Vec<odra::contract_def::Entrypoint> {
vec![
odra::contract_def::Entrypoint {
ident: String::from("init"),
args: vec![
odra::contract_def::Argument {
ident: String::from("total_supply"),
ty: <Option::<U256> as odra::casper_types::CLTyped>::cl_type(),
is_ref: false,
is_slice: false
}
],
is_mut: true,
ret: <() as odra::casper_types::CLTyped>::cl_type(),
ty: odra::contract_def::EntrypointType::Constructor,
attributes: vec![]
},
odra::contract_def::Entrypoint {
ident: String::from("total_supply"),
args: vec![],
is_mut: false,
ret: <U256 as odra::casper_types::CLTyped>::cl_type(),
ty: odra::contract_def::EntrypointType::Public,
attributes: vec![]
},
odra::contract_def::Entrypoint {
ident: String::from("pay_to_mint"),
args: vec![],
is_mut: true,
ret: <() as odra::casper_types::CLTyped>::cl_type(),
ty: odra::contract_def::EntrypointType::Public,
attributes: vec![odra::contract_def::EntrypointAttribute::Payable]
},
odra::contract_def::Entrypoint {
ident: String::from("approve"),
args: vec![
odra::contract_def::Argument {
ident: String::from("to"),
ty: <Address as odra::casper_types::CLTyped>::cl_type(),
is_ref: false,
is_slice: false
},
odra::contract_def::Argument {
ident: String::from("amount"),
ty: <U256 as odra::casper_types::CLTyped>::cl_type(),
is_ref: false,
is_slice: false
}
],
is_mut: true,
ret: <() as odra::casper_types::CLTyped>::cl_type(),
ty: odra::contract_def::EntrypointType::Public,
attributes: vec![odra::contract_def::EntrypointAttribute::NonReentrant]
}
]
}
}
);
let actual = HasEntrypointsImplItem::try_from(&module).unwrap();
test_utils::assert_eq(actual, expected);
}
}
2 changes: 2 additions & 0 deletions odra-macros/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod blueprint;
mod deployer_item;
mod deployer_utils;
mod entrypoints_item;
mod events_item;
mod exec_parts;
mod fn_utils;
Expand All @@ -14,6 +15,7 @@ mod wasm_parts;
mod wasm_parts_utils;

pub(crate) use blueprint::BlueprintItem;
pub(crate) use entrypoints_item::HasEntrypointsImplItem;
pub(crate) use events_item::HasEventsImplItem;
pub(crate) use exec_parts::{ExecPartsItem, ExecPartsReexportItem};
pub(crate) use module_item::ModuleModItem;
Expand Down
10 changes: 10 additions & 0 deletions odra-macros/src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,16 @@ impl FnArgIR {
}
}

pub fn name_str(&self) -> Result<String, syn::Error> {
self.name().map(|i| i.to_string())
}

pub fn ty(&self) -> Result<syn::Type, syn::Error> {
match &self.code {
syn::FnArg::Typed(syn::PatType { box ty, .. }) => Ok(ty.clone()),
_ => Err(syn::Error::new_spanned(&self.code, "Unnamed arg"))
}
}
pub fn name_and_ty(&self) -> Result<(String, syn::Type), syn::Error> {
match &self.code {
syn::FnArg::Typed(syn::PatType {
Expand Down
1 change: 1 addition & 0 deletions odra-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
struct ModuleImpl {
#[expr(item.self_code())]
self_code: syn::ItemImpl,
has_entrypoints_item: HasEntrypointsImplItem,
ref_item: RefItem,
test_parts: TestPartsItem,
test_parts_reexport: TestPartsReexportItem,
Expand Down
5 changes: 5 additions & 0 deletions odra-macros/src/utils/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,8 @@ pub fn events(ty: &syn::Type) -> syn::Expr {
let has_events_ty = super::ty::has_events();
parse_quote!(<#ty as #has_events_ty>::events())
}

pub fn entrypoints(ty: &syn::Type) -> syn::Expr {
let has_entrypoints_ty = super::ty::has_entrypoints();
parse_quote!(<#ty as #has_entrypoints_ty>::entrypoints())
}
28 changes: 28 additions & 0 deletions odra-macros/src/utils/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,38 @@ pub fn has_events() -> syn::Type {
parse_quote!(odra::contract_def::HasEvents)
}

pub fn has_entrypoints() -> syn::Type {
parse_quote!(odra::contract_def::HasEntrypoints)
}

pub fn event() -> syn::Type {
parse_quote!(odra::contract_def::Event)
}

pub fn contract_blueprint() -> syn::Type {
parse_quote!(odra::contract_def::ContractBlueprint)
}

pub fn entry_point_def() -> syn::Type {
parse_quote!(odra::contract_def::Entrypoint)
}

pub fn entry_point_def_attr_payable() -> syn::Type {
parse_quote!(odra::contract_def::EntrypointAttribute::Payable)
}

pub fn entry_point_def_attr_non_reentrant() -> syn::Type {
parse_quote!(odra::contract_def::EntrypointAttribute::NonReentrant)
}

pub fn entry_point_def_ty_constructor() -> syn::Type {
parse_quote!(odra::contract_def::EntrypointType::Constructor)
}

pub fn entry_point_def_ty_public() -> syn::Type {
parse_quote!(odra::contract_def::EntrypointType::Public)
}

pub fn entry_point_def_arg() -> syn::Type {
parse_quote!(odra::contract_def::Argument)
}

0 comments on commit cdf2818

Please sign in to comment.