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

Add interest bearing mint extension #3278

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- cli: Add short alias for the `idl build` command ([#3283](https://github.com/coral-xyz/anchor/pull/3283)).
- cli: Add `--program-id` option to `idl convert` command ([#3309](https://github.com/coral-xyz/anchor/pull/3309)).
- lang: Generate documentation of constants in `declare_program!` ([#3311](https://github.com/coral-xyz/anchor/pull/3311)).
- spl: Add 'Interest Bearing Config` Extension ([#3278](https://github.com/coral-xyz/anchor/pull/3278)).
- cli: Add support for fetching legacy IDLs ([#3324](https://github.com/coral-xyz/anchor/pull/3324)).
- avm: Add short alias for `install` and `list` commands ([#3326](https://github.com/coral-xyz/anchor/pull/3326)).
- avm: Add Windows support for renaming anchor binary ([#3325](https://github.com/coral-xyz/anchor/pull/3325)).
Expand Down
24 changes: 24 additions & 0 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,8 @@ fn generate_constraint_init_group(
metadata_pointer_metadata_address,
close_authority,
permanent_delegate,
interest_bearing_mint_rate,
interest_bearing_mint_authority,
transfer_hook_authority,
transfer_hook_program_id,
} => {
Expand Down Expand Up @@ -805,6 +807,10 @@ fn generate_constraint_init_group(
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::PermanentDelegate});
}

if interest_bearing_mint_rate.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::InterestBearingConfig});
}

let mint_space = if extensions.is_empty() {
quote! { ::anchor_spl::token::Mint::LEN }
} else {
Expand Down Expand Up @@ -864,6 +870,18 @@ fn generate_constraint_init_group(
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
};

let interest_bearing_mint_rate = match interest_bearing_mint_rate {
Some(ibmr) => quote! { Option::<i16>::Some(#ibmr) },
None => quote! { Option::<i16>::None },
};

let interest_bearing_mint_authority = match interest_bearing_mint_authority {
Some(ibma) => {
quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#ibma.key()) }
}
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};

let transfer_hook_authority = match transfer_hook_authority {
Some(tha) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#tha.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
Expand Down Expand Up @@ -946,6 +964,12 @@ fn generate_constraint_init_group(
mint: #field.to_account_info(),
}), #permanent_delegate.unwrap())?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::InterestBearingConfig => {
::anchor_spl::token_interface::interest_bearing_mint_initialize(anchor_lang::context::CpiContext::new(#token_program.to_account_info(), ::anchor_spl::token_interface::InterestBearingMintInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #interest_bearing_mint_authority, #interest_bearing_mint_rate.unwrap())?;
},
// All extensions specified by the user should be implemented.
// If this line runs, it means there is a bug in the codegen.
_ => unimplemented!("{e:?}"),
Expand Down
14 changes: 14 additions & 0 deletions lang/syn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,8 @@ pub enum ConstraintToken {
ExtensionTokenHookAuthority(Context<ConstraintExtensionAuthority>),
ExtensionTokenHookProgramId(Context<ConstraintExtensionTokenHookProgramId>),
ExtensionPermanentDelegate(Context<ConstraintExtensionPermanentDelegate>),
ExtensionInterestBearingMintRate(Context<ConstraintExtensionInterestBearingMintRate>),
ExtensionInterestBearingMintAuthority(Context<ConstraintExtensionAuthority>),
}

impl Parse for ConstraintToken {
Expand Down Expand Up @@ -905,6 +907,11 @@ pub struct ConstraintExtensionAuthority {
pub authority: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintBool {
pub enable: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintExtensionGroupPointerGroupAddress {
pub group_address: Expr,
Expand All @@ -930,6 +937,11 @@ pub struct ConstraintExtensionPermanentDelegate {
pub permanent_delegate: Expr,
}

#[derive(Debug, Clone)]
pub struct ConstraintExtensionInterestBearingMintRate {
pub rate: Expr,
}

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum InitKind {
Expand Down Expand Up @@ -965,6 +977,8 @@ pub enum InitKind {
metadata_pointer_metadata_address: Option<Expr>,
close_authority: Option<Expr>,
permanent_delegate: Option<Expr>,
interest_bearing_mint_rate: Option<Expr>,
interest_bearing_mint_authority: Option<Expr>,
transfer_hook_authority: Option<Expr>,
transfer_hook_program_id: Option<Expr>,
},
Expand Down
74 changes: 74 additions & 0 deletions lang/syn/src/parser/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,35 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
}
}
"interest_bearing_mint" => {
stream.parse::<Token![:]>()?;
stream.parse::<Token![:]>()?;
let kw = stream.call(Ident::parse_any)?.to_string();
stream.parse::<Token![=]>()?;

let span = ident
.span()
.join(stream.span())
.unwrap_or_else(|| ident.span());

match kw.as_str() {
"rate" => ConstraintToken::ExtensionInterestBearingMintRate(Context::new(
span,
ConstraintExtensionInterestBearingMintRate {
rate: stream.parse()?,
},
)),
"authority" => {
ConstraintToken::ExtensionInterestBearingMintAuthority(Context::new(
span,
ConstraintExtensionAuthority {
authority: stream.parse()?,
},
))
}
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
}
}
"transfer_hook" => {
stream.parse::<Token![:]>()?;
stream.parse::<Token![:]>()?;
Expand Down Expand Up @@ -525,6 +554,7 @@ pub struct ConstraintGroupBuilder<'ty> {
pub mint_freeze_authority: Option<Context<ConstraintMintFreezeAuthority>>,
pub mint_decimals: Option<Context<ConstraintMintDecimals>>,
pub mint_token_program: Option<Context<ConstraintTokenProgram>>,
// mint extensions.
pub extension_group_pointer_authority: Option<Context<ConstraintExtensionAuthority>>,
pub extension_group_pointer_group_address:
Option<Context<ConstraintExtensionGroupPointerGroupAddress>>,
Expand All @@ -538,6 +568,9 @@ pub struct ConstraintGroupBuilder<'ty> {
pub extension_transfer_hook_authority: Option<Context<ConstraintExtensionAuthority>>,
pub extension_transfer_hook_program_id: Option<Context<ConstraintExtensionTokenHookProgramId>>,
pub extension_permanent_delegate: Option<Context<ConstraintExtensionPermanentDelegate>>,
pub extension_interest_bearing_mint_rate:
Option<Context<ConstraintExtensionInterestBearingMintRate>>,
pub extension_interest_bearing_mint_authority: Option<Context<ConstraintExtensionAuthority>>,
pub bump: Option<Context<ConstraintTokenBump>>,
pub program_seed: Option<Context<ConstraintProgramSeed>>,
pub realloc: Option<Context<ConstraintRealloc>>,
Expand Down Expand Up @@ -583,6 +616,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
extension_transfer_hook_authority: None,
extension_transfer_hook_program_id: None,
extension_permanent_delegate: None,
extension_interest_bearing_mint_rate: None,
extension_interest_bearing_mint_authority: None,
bump: None,
program_seed: None,
realloc: None,
Expand Down Expand Up @@ -795,6 +830,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
extension_transfer_hook_authority,
extension_transfer_hook_program_id,
extension_permanent_delegate,
extension_interest_bearing_mint_rate,
extension_interest_bearing_mint_authority,
bump,
program_seed,
realloc,
Expand Down Expand Up @@ -975,6 +1012,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
)),
},
token_program: token_token_program.map(|tp| tp.into_inner().token_program),

}
} else if let Some(at) = &associated_token {
InitKind::AssociatedToken {
Expand Down Expand Up @@ -1003,6 +1041,8 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
metadata_pointer_metadata_address: extension_metadata_pointer_metadata_address.map(|mpma| mpma.into_inner().metadata_address),
close_authority: extension_close_authority.map(|ca| ca.into_inner().authority),
permanent_delegate: extension_permanent_delegate.map(|pd| pd.into_inner().permanent_delegate),
interest_bearing_mint_rate: extension_interest_bearing_mint_rate.map(|ibmr| ibmr.into_inner().rate),
interest_bearing_mint_authority: extension_interest_bearing_mint_authority.map(|ibma| ibma.into_inner().authority),
transfer_hook_authority: extension_transfer_hook_authority.map(|tha| tha.into_inner().authority),
transfer_hook_program_id: extension_transfer_hook_program_id.map(|thpid| thpid.into_inner().program_id),
}
Expand Down Expand Up @@ -1093,6 +1133,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
ConstraintToken::ExtensionPermanentDelegate(c) => {
self.add_extension_permanent_delegate(c)
}
ConstraintToken::ExtensionInterestBearingMintRate(c) => {
self.add_extension_interest_bearing_mint_rate(c)
}
ConstraintToken::ExtensionInterestBearingMintAuthority(c) => {
self.add_extension_interest_bearing_mint_authority(c)
}
}
}

Expand Down Expand Up @@ -1675,4 +1721,32 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
self.extension_permanent_delegate.replace(c);
Ok(())
}

fn add_extension_interest_bearing_mint_rate(
&mut self,
c: Context<ConstraintExtensionInterestBearingMintRate>,
) -> ParseResult<()> {
if self.extension_interest_bearing_mint_rate.is_some() {
return Err(ParseError::new(
c.span(),
"extension interest bearing mint rate already provided",
));
}
self.extension_interest_bearing_mint_rate.replace(c);
Ok(())
}

fn add_extension_interest_bearing_mint_authority(
&mut self,
c: Context<ConstraintExtensionAuthority>,
) -> ParseResult<()> {
if self.extension_interest_bearing_mint_authority.is_some() {
return Err(ParseError::new(
c.span(),
"extension interest bearing mint rate authority already provided",
));
}
self.extension_interest_bearing_mint_authority.replace(c);
Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use anchor_spl::{
token_2022::spl_token_2022::extension::{
group_member_pointer::GroupMemberPointer, metadata_pointer::MetadataPointer,
mint_close_authority::MintCloseAuthority, permanent_delegate::PermanentDelegate,
transfer_hook::TransferHook,
transfer_hook::TransferHook, interest_bearing_mint::{InterestBearingConfig, BasisPoints},
},
token_interface::{
get_mint_extension_data, spl_token_metadata_interface::state::TokenMetadata,
Expand Down Expand Up @@ -53,6 +53,8 @@ pub struct CreateMintAccount<'info> {
extensions::transfer_hook::program_id = crate::ID,
extensions::close_authority::authority = authority,
extensions::permanent_delegate::delegate = authority,
extensions::interest_bearing_mint::authority = authority,
extensions::interest_bearing_mint::rate = 100,
)]
pub mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
Expand Down Expand Up @@ -150,6 +152,12 @@ pub fn handler(ctx: Context<CreateMintAccount>, args: CreateMintAccountArgs) ->
group_member_pointer.member_address,
OptionalNonZeroPubkey::try_from(mint_key)?
);
let interest_bearing_config = get_mint_extension_data::<InterestBearingConfig>(mint_data)?;
assert_eq!(
interest_bearing_config.rate_authority,
OptionalNonZeroPubkey::try_from(authority_key)?
);
assert_eq!(interest_bearing_config.current_rate, BasisPoints::from(100));
// transfer minimum rent to mint account
update_account_lamports_to_minimum_balance(
ctx.accounts.mint.to_account_info(),
Expand Down
Loading