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 {

Check warning on line 9 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L9

Added line #L9 was not covered by tests
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")

Check warning on line 15 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L13-L15

Added lines #L13 - L15 were not covered by tests
.to_compile_error()
.into();

Check warning on line 17 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L17

Added line #L17 was not covered by tests
}

// parse the left enum
let mut left: DeriveInput = parse_macro_input!(left);
let Enum(DataEnum {
variants,

Check warning on line 23 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L21-L23

Added lines #L21 - L23 were not covered by tests
..
}) = &mut left.data else {
return syn::Error::new(left.ident.span(), "only enums can accept variants")

Check warning on line 26 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L25-L26

Added lines #L25 - L26 were not covered by tests
.to_compile_error()
.into();

Check warning on line 28 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L28

Added line #L28 was not covered by tests
};

// parse the right enum
let right: DeriveInput = parse_macro_input!(right);
let Enum(DataEnum {
variants: to_add,

Check warning on line 34 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L32-L34

Added lines #L32 - L34 were not covered by tests
..
}) = right.data else {
return syn::Error::new(left.ident.span(), "only enums can provide variants")

Check warning on line 37 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L36-L37

Added lines #L36 - L37 were not covered by tests
.to_compile_error()
.into();

Check warning on line 39 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L39

Added line #L39 was not covered by tests
};

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

Check warning on line 43 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L43

Added line #L43 was not covered by tests

quote! { #left }.into()

Check warning on line 45 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L45

Added line #L45 was not covered by tests
}

/// 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 {

Check warning on line 80 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L80

Added line #L80 was not covered by tests
merge_variants(
metadata,
input,
quote! {

Check warning on line 84 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L82-L84

Added lines #L82 - L84 were not covered by tests
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 {

Check warning on line 134 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L134

Added line #L134 was not covered by tests
merge_variants(
metadata,
input,
quote! {

Check warning on line 138 in packages/mint-hooks/derive/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

packages/mint-hooks/derive/src/lib.rs#L136-L138

Added lines #L136 - L138 were not covered by tests
enum Right {
#[returns(HooksResponse)]
PreMintHooks {},
#[returns(HooksResponse)]
PostMintHooks {},
}
}
.into(),
)
}
Loading
Loading