Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic accessors macro #61

Merged
merged 24 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4ce27c0
initial implementation of getters macro
Artemka374 Apr 12, 2023
048146f
some updates
Artemka374 Apr 13, 2023
a189f4f
add getters
Artemka374 May 5, 2023
7b85f13
fix bug with not compiling OB, compiling but not working for now
varex83 May 15, 2023
e2b838f
fix getters generation
Artemka374 May 16, 2023
242a919
Merge branch 'main' into feature/automatic-getters
Artemka374 May 16, 2023
11923ae
add docs
Artemka374 May 16, 2023
82c6a39
remove unnecesary trait implementations
Artemka374 May 16, 2023
c49acf5
fix warnings and add the getters macro to psp22 example
Artemka374 May 16, 2023
4cd45bc
rename to accessors and add `set` attribute to generate setters
Artemka374 May 17, 2023
5520f21
add necessary code so the tests actuall compile
grandima May 22, 2023
07e06fd
add only_set e2e test
grandima May 22, 2023
ce7e3b4
add get_only e2e test
grandima May 22, 2023
e2a7ceb
create `accessors_attr` folder to demo `accessors` macro
grandima May 23, 2023
8c90917
add more tests to `accessors_attr`;
grandima May 23, 2023
1182cab
rename `HatedLogic` -> `DumbData`
grandima May 23, 2023
0243e16
rename HatedLogic -> HatedStorage
grandima May 23, 2023
47ed286
change from `find` to `any`
grandima May 23, 2023
9538cdc
Merge pull request #66 from Brushfam/grandima/automatic-getters
Artemka374 May 24, 2023
e3f8d8b
Fix doc tests
varex83 May 25, 2023
a25c448
apply suggestions
Artemka374 May 25, 2023
aa5ac3f
Merge branch 'main' into feature/automatic-getters
Artemka374 May 25, 2023
79d800b
apply suggestions
Artemka374 May 29, 2023
f7cc8b9
rename dumb to access
Artemka374 May 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/src/traits/psp22/psp22.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use openbrush::traits::{
#[openbrush::wrapper]
pub type PSP22Ref = dyn PSP22;

/// Trait implemented by all PSP-20 respecting smart traits.
/// Trait implemented by all PSP-22 respecting smart traits.
#[openbrush::trait_definition]
pub trait PSP22 {
/// Returns the total token supply.
Expand Down
57 changes: 36 additions & 21 deletions examples/psp22/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,33 @@ pub mod my_psp22 {
contracts::psp22::*,
traits::{
Storage,
StorageAsMut,
String,
},
};


#[ink(storage)]
#[derive(Storage)]
pub struct Contract {
#[storage_field]
psp22: psp22::Data,
// fields for hater logic
#[storage_field]
hated_logic: HatedLogic,
}

#[openbrush::upgradeable_storage(STORAGE_KEY)]
#[openbrush::getters(HatedLogicGetters)]
#[derive(Debug)]
pub struct HatedLogic {
#[get]
hated_account: AccountId,
}

pub const STORAGE_KEY: u32 = openbrush::storage_unique_key!(HatedLogic);

impl HatedLogicGetters for Contract {}

impl Transfer for Contract {
// Let's override method to reject transactions to bad account
fn _before_token_transfer(
Expand All @@ -31,7 +44,7 @@ pub mod my_psp22 {
to: Option<&AccountId>,
_amount: &Balance,
) -> Result<(), PSP22Error> {
if to == Some(&self.hated_account) {
if to == Some(&self.data::<HatedLogic>().hated_account) {
return Err(PSP22Error::Custom(String::from("I hate this account!")))
}
Ok(())
Expand All @@ -45,7 +58,9 @@ pub mod my_psp22 {
pub fn new(total_supply: Balance) -> Self {
let mut instance = Self {
psp22: Default::default(),
hated_account: [255; 32].into(),
hated_logic: HatedLogic {
hated_account: [255; 32].into(),
},
};

instance
Expand All @@ -57,12 +72,7 @@ pub mod my_psp22 {

#[ink(message)]
pub fn set_hated_account(&mut self, hated: AccountId) {
self.hated_account = hated;
}

#[ink(message)]
pub fn get_hated_account(&self) -> AccountId {
self.hated_account.clone()
self.data::<HatedLogic>().hated_account = hated;
}
}

Expand All @@ -76,15 +86,16 @@ pub mod my_psp22 {

use test_helpers::{
address_of,
balance_of
balance_of,
};

type E2EResult<T> = Result<T, Box<dyn std::error::Error>>;

#[ink_e2e::test]
async fn assigns_initial_balance(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
let constructor = ContractRef::new(100);
let address = client.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
let address = client
.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;
Expand All @@ -103,15 +114,17 @@ pub mod my_psp22 {
#[ink_e2e::test]
async fn transfer_adds_amount_to_destination_account(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
let constructor = ContractRef::new(100);
let address = client.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
let address = client
.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;

let result = {
let _msg = build_message::<ContractRef>(address.clone())
.call(|contract| contract.transfer(address_of!(bob), 50, vec![]));
client.call(&ink_e2e::alice(), _msg, 0, None)
client
.call(&ink_e2e::alice(), _msg, 0, None)
.await
.expect("transfer failed")
};
Expand All @@ -131,16 +144,16 @@ pub mod my_psp22 {
#[ink_e2e::test]
async fn cannot_transfer_above_the_amount(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
let constructor = ContractRef::new(100);
let address = client.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
let address = client
.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;

let result = {
let _msg = build_message::<ContractRef>(address.clone())
.call(|contract| contract.transfer(address_of!(bob), 101, vec![]));
client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None)
.await
client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None).await
};

assert!(matches!(result.return_value(), Err(PSP22Error::InsufficientBalance)));
Expand All @@ -151,15 +164,17 @@ pub mod my_psp22 {
#[ink_e2e::test]
async fn cannot_transfer_to_hated_account(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
let constructor = ContractRef::new(100);
let address = client.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
let address = client
.instantiate("my_psp22", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;

let result = {
let _msg = build_message::<ContractRef>(address.clone())
.call(|contract| contract.transfer(address_of!(bob), 10, vec![]));
client.call(&ink_e2e::alice(), _msg, 0, None)
client
.call(&ink_e2e::alice(), _msg, 0, None)
.await
.expect("transfer failed")
};
Expand All @@ -173,7 +188,8 @@ pub mod my_psp22 {
let result = {
let _msg = build_message::<ContractRef>(address.clone())
.call(|contract| contract.set_hated_account(address_of!(bob)));
client.call(&ink_e2e::alice(), _msg, 0, None)
client
.call(&ink_e2e::alice(), _msg, 0, None)
.await
.expect("set_hated_account failed")
};
Expand All @@ -183,8 +199,7 @@ pub mod my_psp22 {
let result = {
let _msg = build_message::<ContractRef>(address.clone())
.call(|contract| contract.transfer(address_of!(bob), 10, vec![]));
client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None)
.await
client.call_dry_run(&ink_e2e::alice(), &_msg, 0, None).await
};

assert!(matches!(result.return_value(), Err(PSP22Error::Custom(_))));
Expand Down
122 changes: 122 additions & 0 deletions lang/codegen/src/getters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use proc_macro2::TokenStream;
use quote::{
quote,
quote_spanned,
};
use syn::{
spanned::Spanned,
Data,
DataStruct,
Field,
Fields,
};

pub fn getters(attrs: TokenStream, s: synstructure::Structure) -> TokenStream {
let trait_ident = attrs.clone();

let struct_ident = s.ast().ident.clone();

let item = match s.ast().data.clone() {
Data::Struct(struct_item) => generate_struct(&s, struct_item),
_ => panic!("Only structs are supported"),
};

let fields: Vec<_> = extract_fields(s.clone());

let trait_messages = fields.iter().map(|field| {
let field_ident = field.ident.clone().unwrap();
let field_type = field.ty.clone();
let span = field.span();

quote_spanned! {span =>
#[ink(message)]
fn #field_ident(&self) -> #field_type;
}
});

let impls = fields.iter().map(|field| {
let field_ident = field.ident.clone().unwrap();
let field_type = field.ty.clone();
let span = field.span();

quote_spanned! {span =>
default fn #field_ident(&self) -> #field_type {
self.data().#field_ident
}
}
});

(quote! {
#item

#[openbrush::trait_definition]
pub trait #trait_ident {
#(#trait_messages)*
}

impl<T: Storage<#struct_ident>> #trait_ident for T {
#(#impls)*
}
})
.into()
}

fn generate_struct(s: &synstructure::Structure, struct_item: DataStruct) -> TokenStream {
let struct_ident = s.ast().ident.clone();
let vis = s.ast().vis.clone();
let attrs = s.ast().attrs.clone();
let types = s.ast().generics.clone();
let (_, _, where_closure) = s.ast().generics.split_for_impl();

let fields = struct_item
.clone()
.fields
.into_iter()
.map(|field| consume_getter_attrs(&mut field.clone()));

match struct_item.fields {
Fields::Unnamed(_) => {
quote! {
#(#attrs)*
#vis struct #struct_ident #types #where_closure (
#(#fields),*
);
}
}
_ => {
quote! {
#(#attrs)*
#vis struct #struct_ident #types #where_closure {
#(#fields),*
}
}
}
}
}

fn consume_getter_attrs(field: &mut syn::Field) -> Field {
let attr = field
.attrs
.iter()
.filter(|a| !a.path.is_ident("get"))
.cloned()
.collect();

field.attrs = attr;

field.clone()
}

fn extract_fields(s: synstructure::Structure) -> Vec<Field> {
let struct_item = match s.ast().data.clone() {
Data::Struct(struct_item) => struct_item,
_ => panic!("Only structs are supported"),
};

struct_item
.fields
.iter()
.filter(|field| field.attrs.iter().find(|a| a.path.is_ident("get")).is_some())
.cloned()
.collect::<Vec<_>>()
}
1 change: 1 addition & 0 deletions lang/codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

pub mod contract;
pub mod getters;
pub mod internal;
pub mod metadata;
pub mod modifier_definition;
Expand Down
39 changes: 39 additions & 0 deletions lang/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use proc_macro::TokenStream;

use openbrush_lang_codegen::{
contract,
getters,
modifier_definition,
modifiers,
storage,
Expand Down Expand Up @@ -475,3 +476,41 @@ synstructure::decl_attribute!(
pub fn storage_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
storage_derive::storage_derive(item.into()).into()
}

synstructure::decl_attribute!(
[getters] =>
/// Macro that automatically implements getters for struct fields, that implements scale::Encode
/// and scale::Decode traits. You should specify the getters trait naming in the macro's attribute.
/// Also, fields that you want getters to be generated, should be marked by `#[get]` attribute.
/// The name of the getter message will be the same as field's name.
///
/// # Example:
/// ```
/// {
/// use openbrush::traits::Storage;
///
/// #[ink(storage)]
/// #[derive(Storage, Default)]
/// pub struct Contract {
/// #[storage_field]
/// some_struct: SomeStruct,
/// }
///
/// pub const STORAGE_KEY: u32 = openbrush::storage_unique_key!(SomeStruct);
///
/// #[openbrush::upgradeable_storage(STORAGE_KEY)]
/// #[openbrush::getters(SomeStructGetters)]
/// #[derive(Default)]
/// pub struct SomeStruct {
/// #[get]
/// a: u32,
/// b: u32,
/// #[get]
/// c: u32,
/// }
///
/// impl SomeStructGetters for Contract {}
/// }
/// ```
getters::getters
);
1 change: 1 addition & 0 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod utils;

pub use openbrush_lang_macro::{
contract,
getters,
modifier_definition,
modifiers,
trait_definition,
Expand Down