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

Mint hooks #612

Merged
merged 9 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ sg721-nt = { version = "3.1.0", path = "contracts/collections/sg721-
sg721-updatable = { version = "3.1.0", path = "contracts/collections/sg721-updatable" }
sg-controllers = { version = "3.1.0", path = "packages/controllers" }
sg-metadata = { version = "3.1.0", path = "packages/sg-metadata" }
sg-mint-hooks = { version = "3.1.0", path = "packages/mint-hooks" }
sg-multi-test = { version = "3.1.0" }
sg-splits = { version = "3.1.0", path = "contracts/splits" }
sg-std = { version = "3.1.0" }
Expand Down
20 changes: 20 additions & 0 deletions packages/mint-hooks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "sg-mint-hooks"
authors = ["Shane Vitarana <s@users.noreply.publicawesome.com>"]
description = "Mint hooks to allow extending minting contracts"
version = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
license = { workspace = true }

[dependencies]
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-storage-plus = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
sg-controllers = { workspace = true }
sg-std = { workspace = true }
thiserror = { workspace = true }
sg-mint-hooks-derive = { path = "./derive", version = "0.1.0" }
60 changes: 60 additions & 0 deletions packages/mint-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Stargaze Mint Hooks

Mint hooks are a way to run custom code before and after minting. Creators may write custom hooks to perform tasks such as implementing burn-to-mint.

For example, a pre-mint hook could receive an NFT and check if its in the collection to be burned. Then after the mint is complete, the post-mint hook could do the actual burn operation.

The pre-mint action, mint, and post-mint actions are sub-messages that are implemented as a single atomic action. They are executed in order, and all rollback if one of them fails.

## How to add mint hooks to a minter contract

### Add macros for execute and query enums

```rs
#[sg_mint_hooks_execute]
#[cw_serde]
pub enum ExecuteMsg {}

#[sg_mint_hooks_query]
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {}
```

### Add the pre and post submessages to mint execution

```rs
let premint_hooks = prepare_premint_hooks(
deps.as_ref(),
collection.clone(),
Some(token_id.clone()),
info.sender.to_string(),
)?;

let postmint_hooks = prepare_postmint_hooks(
deps.as_ref(),
collection.clone(),
Some(token_id.clone()),
info.sender.to_string(),
)?;

let mint = WasmMsg::Execute { ... };

Response::new()
.add_submessages(premint_hooks)
.add_submessage(SubMsg::reply_on_error(mint, MINT_REPLY_ID));
.add_submessages(postmint_hooks);
```

### Handle the reply errors

```rs
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
handle_reply(msg.id)?;

match msg.id {
MINT_REPLY_ID => Err(ContractError::MintFailed {}),
}

}
```
19 changes: 19 additions & 0 deletions packages/mint-hooks/derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "sg-mint-hooks-derive"
version = "0.1.0"
description = "Macros for generating code used by the `sg-mint-hooks` crate"
authors = ["Shane Vitarana <s@users.noreply.publicawesome.com>"]
edition = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
license = { workspace = true }

[lib]
doctest = false # disable doc tests
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "1"
sg-controllers = { workspace = true }
13 changes: 13 additions & 0 deletions packages/mint-hooks/derive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SG Mint Hooks Derive

Adds macros for execute and query enums.

```rs
#[sg_mint_hooks_execute]
```

```rs
#[sg_mint_hooks_query]
```

These can be added to minters to enforce mint hooks.
148 changes: 148 additions & 0 deletions packages/mint-hooks/derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput};

/// Merges the variants of two enums.
///
/// Adapted from DAO DAO:
/// https://github.com/DA0-DA0/dao-contracts/blob/74bd3881fdd86829e5e8b132b9952dd64f2d0737/packages/dao-macros/src/lib.rs#L9
fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) -> TokenStream {
use syn::Data::Enum;

// parse metadata
let args = parse_macro_input!(metadata as AttributeArgs);
if let Some(first_arg) = args.first() {
return syn::Error::new_spanned(first_arg, "macro takes no arguments")
.to_compile_error()
.into();
}

// parse the left enum
let mut left: DeriveInput = parse_macro_input!(left);
let Enum(DataEnum {
variants,
..
}) = &mut left.data else {
return syn::Error::new(left.ident.span(), "only enums can accept variants")
.to_compile_error()
.into();
};

// parse the right enum
let right: DeriveInput = parse_macro_input!(right);
let Enum(DataEnum {
variants: to_add,
..
}) = right.data else {
return syn::Error::new(left.ident.span(), "only enums can provide variants")
.to_compile_error()
.into();
};

// insert variants from the right to the left
variants.extend(to_add.into_iter());

quote! { #left }.into()
}

/// Append ownership-related execute message variant(s) to an enum.
///
/// For example, apply the `sg_mint_hooks_execute` macro to the following enum:
///
/// ```rust
/// use cosmwasm_schema::cw_serde;
/// use sg_mint_hooks::sg_mint_hooks_exeucte;
///
/// #[sg_mint_hooks_execute]
/// #[cw_serde]
/// enum ExecuteMsg {
/// Foo {},
/// Bar {},
/// }
/// ```
///
/// Is equivalent to:
///
/// ```rust
/// use cosmwasm_schema::cw_serde;
///
/// #[cw_serde]
/// enum ExecuteMsg {
/// AddPreMintHook { hook: String },
/// AddPostMintHook { hook: String },
/// Foo {},
/// Bar {},
/// }
/// ```
///
/// Note: `#[sg_mint_hooks_execute]` must be applied _before_ `#[cw_serde]`.
#[proc_macro_attribute]
pub fn sg_mint_hooks_execute(metadata: TokenStream, input: TokenStream) -> TokenStream {
merge_variants(
metadata,
input,
quote! {
enum Right {
AddPreMintHook { hook: String },
AddPostMintHook { hook: String },
}
}
.into(),
)
}

/// Append mint hooks related query message variant(s) to an enum.
///
/// For example, apply the `sg_mint_hooks_query` macro to the following enum:
///
/// ```rust
/// use cosmwasm_schema::{cw_serde, QueryResponses};
/// use sg_mint_hooks::sg_mint_hooks_query;
///
/// #[sg_mint_hooks_query]
/// #[cw_serde]
/// #[derive(QueryResponses)]
/// enum QueryMsg {
/// #[returns(FooResponse)]
/// Foo {},
/// #[returns(BarResponse)]
/// Bar {},
/// }
/// ```
///
/// Is equivalent to:
///
/// ```rust
/// use cosmwasm_schema::cw_serde;
///
/// #[cw_serde]
/// #[derive(QueryResponses)]
/// enum QueryMsg {
/// #[returns(HooksResponse)]
/// PreMintHooks {},
/// #[returns(HooksResponse)]
/// PostMintHooks {},
/// #[returns(FooResponse)]
/// Foo {},
/// #[returns(BarResponse)]
/// Bar {},
/// }
/// ```
///
/// Note: `#[sg_mint_hooks_query]` must be applied _before_ `#[cw_serde]`.
#[proc_macro_attribute]
pub fn sg_mint_hooks_query(metadata: TokenStream, input: TokenStream) -> TokenStream {
merge_variants(
metadata,
input,
quote! {
enum Right {
#[returns(HooksResponse)]
PreMintHooks {},
#[returns(HooksResponse)]
PostMintHooks {},
}
}
.into(),
)
}
Loading