diff --git a/cross-chain/solana/Cargo.lock b/cross-chain/solana/Cargo.lock index 8aa7d04c6..b01fb264b 100644 --- a/cross-chain/solana/Cargo.lock +++ b/cross-chain/solana/Cargo.lock @@ -2066,6 +2066,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "wormhole-anchor-sdk" +version = "0.1.0" +source = "git+https://github.com/wormhole-foundation/wormhole-scaffolding?rev=f8d5ba04bfd449ab3693b15c818fd3e85e30f758#f8d5ba04bfd449ab3693b15c818fd3e85e30f758" +dependencies = [ + "anchor-lang", + "anchor-spl", + "cfg-if", +] + [[package]] name = "wormhole-gateway" version = "0.1.0" @@ -2073,6 +2083,7 @@ dependencies = [ "anchor-lang", "anchor-spl", "tbtc", + "wormhole-anchor-sdk", ] [[package]] diff --git a/cross-chain/solana/programs/tbtc/src/lib.rs b/cross-chain/solana/programs/tbtc/src/lib.rs index 94a987451..593302351 100644 --- a/cross-chain/solana/programs/tbtc/src/lib.rs +++ b/cross-chain/solana/programs/tbtc/src/lib.rs @@ -13,6 +13,15 @@ use anchor_lang::prelude::*; declare_id!("HksEtDgsXJV1BqcuhzbLRTmXp5gHgHJktieJCtQd3pG"); +#[derive(Clone)] +pub struct Tbtc; + +impl Id for Tbtc { + fn id() -> Pubkey { + ID + } +} + #[program] pub mod tbtc { use super::*; @@ -29,16 +38,16 @@ pub mod tbtc { processor::add_minter(ctx) } - pub fn remove_minter(ctx: Context, minter: Pubkey) -> Result<()> { - processor::remove_minter(ctx, minter) + pub fn remove_minter(ctx: Context) -> Result<()> { + processor::remove_minter(ctx) } pub fn add_guardian(ctx: Context) -> Result<()> { processor::add_guardian(ctx) } - pub fn remove_guardian(ctx: Context, guardian: Pubkey) -> Result<()> { - processor::remove_guardian(ctx, guardian) + pub fn remove_guardian(ctx: Context) -> Result<()> { + processor::remove_guardian(ctx) } pub fn pause(ctx: Context) -> Result<()> { diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/add_guardian.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/add_guardian.rs index cd79f2d5e..422bac905 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/add_guardian.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/add_guardian.rs @@ -1,6 +1,6 @@ use crate::{ error::TbtcError, - state::{GuardianInfo, Tbtc}, + state::{Config, GuardianInfo}, }; use anchor_lang::prelude::*; @@ -8,30 +8,36 @@ use anchor_lang::prelude::*; pub struct AddGuardian<'info> { #[account( mut, + seeds = [Config::SEED_PREFIX], + bump, has_one = authority @ TbtcError::IsNotAuthority )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, - /// CHECK: the guardian does not need to sign - pub guardian: UncheckedAccount<'info>, + config: Account<'info, Config>, + #[account(mut)] - pub payer: Signer<'info>, + authority: Signer<'info>, + #[account( init, - payer = payer, - space = GuardianInfo::MAXIMUM_SIZE, - seeds = [GuardianInfo::SEED_PREFIX, tbtc.key().as_ref(), guardian.key().as_ref()], bump + payer = authority, + space = 8 + GuardianInfo::INIT_SPACE, + seeds = [GuardianInfo::SEED_PREFIX, guardian.key().as_ref()], + bump )] - pub guardian_info: Account<'info, GuardianInfo>, - pub system_program: Program<'info, System>, + guardian_info: Account<'info, GuardianInfo>, + + /// CHECK: Required authority to pause contract. This pubkey lives in `GuardianInfo`. + guardian: AccountInfo<'info>, + + system_program: Program<'info, System>, } pub fn add_guardian(ctx: Context) -> Result<()> { ctx.accounts.guardian_info.set_inner(GuardianInfo { guardian: ctx.accounts.guardian.key(), - bump: *ctx.bumps.get("guardian_info").unwrap(), + bump: ctx.bumps["guardian_info"], }); - ctx.accounts.tbtc.guardians += 1; + ctx.accounts.config.num_guardians += 1; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/add_minter.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/add_minter.rs index f263dafe6..ccb8620cb 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/add_minter.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/add_minter.rs @@ -1,6 +1,6 @@ use crate::{ error::TbtcError, - state::{MinterInfo, Tbtc}, + state::{Config, MinterInfo}, }; use anchor_lang::prelude::*; @@ -8,30 +8,36 @@ use anchor_lang::prelude::*; pub struct AddMinter<'info> { #[account( mut, + seeds = [Config::SEED_PREFIX], + bump, has_one = authority @ TbtcError::IsNotAuthority )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, - /// CHECK: the minter does not need to sign - pub minter: UncheckedAccount<'info>, + config: Account<'info, Config>, + #[account(mut)] - pub payer: Signer<'info>, + authority: Signer<'info>, + #[account( init, - payer = payer, - space = MinterInfo::MAXIMUM_SIZE, - seeds = [MinterInfo::SEED_PREFIX, tbtc.key().as_ref(), minter.key().as_ref()], bump + payer = authority, + space = 8 + MinterInfo::INIT_SPACE, + seeds = [MinterInfo::SEED_PREFIX, minter.key().as_ref()], + bump )] - pub minter_info: Account<'info, MinterInfo>, - pub system_program: Program<'info, System>, + minter_info: Account<'info, MinterInfo>, + + /// CHECK: Required authority to mint tokens. This pubkey lives in `MinterInfo`. + minter: AccountInfo<'info>, + + system_program: Program<'info, System>, } pub fn add_minter(ctx: Context) -> Result<()> { ctx.accounts.minter_info.set_inner(MinterInfo { minter: ctx.accounts.minter.key(), - bump: *ctx.bumps.get("minter_info").unwrap(), + bump: ctx.bumps["minter_info"], }); - ctx.accounts.tbtc.minters += 1; + ctx.accounts.config.num_minters += 1; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/change_authority.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/change_authority.rs index 2526c88f7..d2d0d1669 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/change_authority.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/change_authority.rs @@ -1,20 +1,22 @@ -use crate::{error::TbtcError, state::Tbtc}; +use crate::{error::TbtcError, state::Config}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct ChangeAuthority<'info> { #[account( mut, + seeds = [Config::SEED_PREFIX], + bump, has_one = authority @ TbtcError::IsNotAuthority )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, - #[account(mut)] - pub payer: Signer<'info>, - pub new_authority: Signer<'info>, + config: Account<'info, Config>, + + authority: Signer<'info>, + + new_authority: Signer<'info>, } pub fn change_authority(ctx: Context) -> Result<()> { - ctx.accounts.tbtc.authority = ctx.accounts.new_authority.key(); + ctx.accounts.config.authority = ctx.accounts.new_authority.key(); Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/initialize.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/initialize.rs index d4feff73b..9b418ddfd 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/initialize.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/initialize.rs @@ -1,4 +1,4 @@ -use crate::{constants::SEED_PREFIX_TBTC_MINT, state::Tbtc}; +use crate::{constants::SEED_PREFIX_TBTC_MINT, state::Config}; use anchor_lang::prelude::*; use anchor_spl::token; @@ -8,31 +8,38 @@ pub struct Initialize<'info> { // so we can sign for it from the program #[account( init, - seeds = [SEED_PREFIX_TBTC_MINT, tbtc.key().as_ref()], + seeds = [SEED_PREFIX_TBTC_MINT], bump, payer = authority, mint::decimals = 9, - mint::authority = tbtc_mint, + mint::authority = config, )] - pub tbtc_mint: Account<'info, token::Mint>, + mint: Account<'info, token::Mint>, - #[account(init, payer = authority, space = Tbtc::MAXIMUM_SIZE)] - pub tbtc: Account<'info, Tbtc>, + #[account( + init, + payer = authority, + space = 8 + Config::INIT_SPACE, + seeds = [Config::SEED_PREFIX], + bump, + )] + config: Account<'info, Config>, #[account(mut)] - pub authority: Signer<'info>, + authority: Signer<'info>, - pub token_program: Program<'info, token::Token>, - pub system_program: Program<'info, System>, + token_program: Program<'info, token::Token>, + system_program: Program<'info, System>, } pub fn initialize(ctx: Context) -> Result<()> { - ctx.accounts.tbtc.set_inner(Tbtc { + ctx.accounts.config.set_inner(Config { + bump: ctx.bumps["config"], authority: ctx.accounts.authority.key(), - token_mint: ctx.accounts.tbtc_mint.key(), - token_bump: *ctx.bumps.get("tbtc_mint").unwrap(), - minters: 0, - guardians: 0, + mint: ctx.accounts.mint.key(), + mint_bump: ctx.bumps["mint"], + num_minters: 0, + num_guardians: 0, paused: false, }); Ok(()) diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs index 798b2b79f..239635a62 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs @@ -1,6 +1,6 @@ use crate::{ error::TbtcError, - state::{GuardianInfo, Tbtc}, + state::{Config, GuardianInfo}, }; use anchor_lang::prelude::*; @@ -8,19 +8,31 @@ use anchor_lang::prelude::*; pub struct Pause<'info> { #[account( mut, - constraint = !tbtc.paused @ TbtcError::IsPaused + seeds = [Config::SEED_PREFIX], + bump, )] - pub tbtc: Account<'info, Tbtc>, + config: Account<'info, Config>, + #[account( has_one = guardian, - seeds = [GuardianInfo::SEED_PREFIX, tbtc.key().as_ref(), guardian.key().as_ref()], + seeds = [GuardianInfo::SEED_PREFIX, guardian.key().as_ref()], bump = guardian_info.bump )] - pub guardian_info: Account<'info, GuardianInfo>, - pub guardian: Signer<'info>, + guardian_info: Account<'info, GuardianInfo>, + + guardian: Signer<'info>, +} + +impl<'info> Pause<'info> { + fn constraints(ctx: &Context) -> Result<()> { + require!(!ctx.accounts.config.paused, TbtcError::IsPaused); + + Ok(()) + } } +#[access_control(Pause::constraints(&ctx))] pub fn pause(ctx: Context) -> Result<()> { - ctx.accounts.tbtc.paused = true; + ctx.accounts.config.paused = true; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/remove_guardian.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/remove_guardian.rs index 9fb2f1aea..777edf865 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/remove_guardian.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/remove_guardian.rs @@ -1,29 +1,33 @@ use crate::{ error::TbtcError, - state::{GuardianInfo, Tbtc}, + state::{Config, GuardianInfo}, }; use anchor_lang::prelude::*; #[derive(Accounts)] -#[instruction(guardian: Pubkey)] pub struct RemoveGuardian<'info> { #[account( mut, has_one = authority @ TbtcError::IsNotAuthority, )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, + config: Account<'info, Config>, + + authority: Signer<'info>, + #[account( mut, has_one = guardian, close = authority, - seeds = [GuardianInfo::SEED_PREFIX, tbtc.key().as_ref(), guardian.as_ref()], + seeds = [GuardianInfo::SEED_PREFIX, guardian.key().as_ref()], bump = guardian_info.bump, )] - pub guardian_info: Account<'info, GuardianInfo>, + guardian_info: Account<'info, GuardianInfo>, + + /// CHECK: Required authority to pause contract. This pubkey lives in `GuardianInfo`. + guardian: AccountInfo<'info>, } -pub fn remove_guardian(ctx: Context, _guardian: Pubkey) -> Result<()> { - ctx.accounts.tbtc.guardians -= 1; +pub fn remove_guardian(ctx: Context) -> Result<()> { + ctx.accounts.config.num_guardians -= 1; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/remove_minter.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/remove_minter.rs index 9ed557727..83aa5a07d 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/remove_minter.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/remove_minter.rs @@ -1,29 +1,35 @@ use crate::{ error::TbtcError, - state::{MinterInfo, Tbtc}, + state::{Config, MinterInfo}, }; use anchor_lang::prelude::*; #[derive(Accounts)] -#[instruction(minter: Pubkey)] pub struct RemoveMinter<'info> { #[account( mut, + seeds = [Config::SEED_PREFIX], + bump, has_one = authority @ TbtcError::IsNotAuthority )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, + config: Account<'info, Config>, + + authority: Signer<'info>, + #[account( mut, - constraint = minter_info.minter == minter, + has_one = minter, close = authority, - seeds = [MinterInfo::SEED_PREFIX, tbtc.key().as_ref(), minter.as_ref()], + seeds = [MinterInfo::SEED_PREFIX, minter.key().as_ref()], bump = minter_info.bump, )] - pub minter_info: Account<'info, MinterInfo>, + minter_info: Account<'info, MinterInfo>, + + /// CHECK: Required authority to mint tokens. This pubkey lives in `MinterInfo`. + minter: AccountInfo<'info>, } -pub fn remove_minter(ctx: Context, _minter: Pubkey) -> Result<()> { - ctx.accounts.tbtc.minters -= 1; +pub fn remove_minter(ctx: Context) -> Result<()> { + ctx.accounts.config.num_minters -= 1; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs b/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs index a8c46c759..e5ce8fa25 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs @@ -1,18 +1,29 @@ -use crate::{error::TbtcError, state::Tbtc}; +use crate::{error::TbtcError, state::Config}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct Unpause<'info> { #[account( mut, - constraint = tbtc.paused @ TbtcError::IsNotPaused, - has_one = authority @ TbtcError::IsNotAuthority + has_one = authority @ TbtcError::IsNotAuthority, + seeds = [Config::SEED_PREFIX], + bump, )] - pub tbtc: Account<'info, Tbtc>, - pub authority: Signer<'info>, + config: Account<'info, Config>, + + authority: Signer<'info>, +} + +impl<'info> Unpause<'info> { + fn constraints(ctx: &Context) -> Result<()> { + require!(ctx.accounts.config.paused, TbtcError::IsNotPaused); + + Ok(()) + } } +#[access_control(Unpause::constraints(&ctx))] pub fn unpause(ctx: Context) -> Result<()> { - ctx.accounts.tbtc.paused = false; + ctx.accounts.config.paused = false; Ok(()) } diff --git a/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs b/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs index aaf3517a9..f64307754 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs @@ -1,73 +1,69 @@ use crate::{ constants::SEED_PREFIX_TBTC_MINT, error::TbtcError, - state::{MinterInfo, Tbtc}, + state::{Config, MinterInfo}, }; use anchor_lang::prelude::*; -use anchor_spl::{associated_token::AssociatedToken, token}; +use anchor_spl::token; #[derive(Accounts)] pub struct Mint<'info> { // Use the correct token mint for the program. #[account( mut, - seeds = [SEED_PREFIX_TBTC_MINT, tbtc.key().as_ref()], - bump = tbtc.token_bump, - mint::decimals = 9, - mint::authority = tbtc_mint, + seeds = [SEED_PREFIX_TBTC_MINT], + bump = config.mint_bump, + mint::authority = config, )] - pub tbtc_mint: Account<'info, token::Mint>, + mint: Account<'info, token::Mint>, - // Can not mint when paused. #[account( - constraint = !tbtc.paused @ TbtcError::IsPaused + seeds = [Config::SEED_PREFIX], + bump = config.bump, )] - pub tbtc: Account<'info, Tbtc>, + config: Account<'info, Config>, // Require the signing minter to match a valid minter info. #[account( has_one = minter, - seeds = [MinterInfo::SEED_PREFIX, tbtc.key().as_ref(), minter.key().as_ref()], + seeds = [MinterInfo::SEED_PREFIX, minter.key().as_ref()], bump = minter_info.bump, )] - pub minter_info: Account<'info, MinterInfo>, - pub minter: Signer<'info>, + minter_info: Account<'info, MinterInfo>, + + minter: Signer<'info>, // Use the associated token account for the recipient. #[account( - init_if_needed, - payer = payer, - associated_token::mint = tbtc_mint, - associated_token::authority = recipient, + mut, + token::mint = mint, )] - pub recipient_account: Account<'info, token::TokenAccount>, - /// CHECK: the recipient doesn't need to sign the mint, - /// and it doesn't conform to any specific rules. - /// Validating the recipient is the minter's responsibility. - pub recipient: UncheckedAccount<'info>, + recipient_token: Account<'info, token::TokenAccount>, - #[account(mut)] - pub payer: Signer<'info>, + token_program: Program<'info, token::Token>, +} - pub token_program: Program<'info, token::Token>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub system_program: Program<'info, System>, +impl<'info> Mint<'info> { + fn constraints(ctx: &Context) -> Result<()> { + // Can not mint when paused. + require!(!ctx.accounts.config.paused, TbtcError::IsPaused); + + Ok(()) + } } +#[access_control(Mint::constraints(&ctx))] pub fn mint(ctx: Context, amount: u64) -> Result<()> { - let seed_prefix = SEED_PREFIX_TBTC_MINT; - let key_seed = ctx.accounts.tbtc.key(); - let mint_bump = ctx.accounts.tbtc.token_bump; - let signer: &[&[&[u8]]] = &[&[seed_prefix, key_seed.as_ref(), &[mint_bump]]]; - - let cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), - token::MintTo { - mint: ctx.accounts.tbtc_mint.to_account_info(), - to: ctx.accounts.recipient_account.to_account_info(), - authority: ctx.accounts.tbtc_mint.to_account_info(), - }, - signer, - ); - token::mint_to(cpi_ctx, amount) + token::mint_to( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + token::MintTo { + mint: ctx.accounts.mint.to_account_info(), + to: ctx.accounts.recipient_token.to_account_info(), + authority: ctx.accounts.config.to_account_info(), + }, + &[&[Config::SEED_PREFIX, &[ctx.accounts.config.bump]]], + ), + amount, + ) } diff --git a/cross-chain/solana/programs/tbtc/src/state/config.rs b/cross-chain/solana/programs/tbtc/src/state/config.rs new file mode 100644 index 000000000..4dd81b69c --- /dev/null +++ b/cross-chain/solana/programs/tbtc/src/state/config.rs @@ -0,0 +1,23 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(Debug, InitSpace)] +pub struct Config { + pub bump: u8, + + /// The authority over this program. + pub authority: Pubkey, + + // Mint info. + pub mint: Pubkey, + pub mint_bump: u8, + + // Admin info. + pub num_minters: u8, + pub num_guardians: u8, + pub paused: bool, +} + +impl Config { + pub const SEED_PREFIX: &'static [u8] = b"config"; +} diff --git a/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs b/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs index 656b767bb..34f1492ba 100644 --- a/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs +++ b/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs @@ -1,17 +1,12 @@ use anchor_lang::prelude::*; #[account] -#[derive(Default)] +#[derive(Debug, InitSpace)] pub struct GuardianInfo { pub guardian: Pubkey, pub bump: u8, } impl GuardianInfo { - pub fn is_guardian(&self, key: &Pubkey) -> bool { - self.guardian == *key - } - - pub const MAXIMUM_SIZE: usize = 8 + 32 + 1; // discriminator + pubkey + bump - pub const SEED_PREFIX: &'static [u8; 13] = b"guardian-info"; + pub const SEED_PREFIX: &'static [u8] = b"guardian-info"; } diff --git a/cross-chain/solana/programs/tbtc/src/state/minter_info.rs b/cross-chain/solana/programs/tbtc/src/state/minter_info.rs index 978f6ff9d..e8b6a6eeb 100644 --- a/cross-chain/solana/programs/tbtc/src/state/minter_info.rs +++ b/cross-chain/solana/programs/tbtc/src/state/minter_info.rs @@ -1,17 +1,12 @@ use anchor_lang::prelude::*; #[account] -#[derive(Default)] +#[derive(Debug, InitSpace)] pub struct MinterInfo { pub minter: Pubkey, pub bump: u8, } impl MinterInfo { - pub fn is_minter(&self, key: &Pubkey) -> bool { - self.minter == *key - } - - pub const MAXIMUM_SIZE: usize = 8 + 32 + 1; // discriminator + pubkey + bump - pub const SEED_PREFIX: &'static [u8; 11] = b"minter-info"; + pub const SEED_PREFIX: &'static [u8] = b"minter-info"; } diff --git a/cross-chain/solana/programs/tbtc/src/state/mod.rs b/cross-chain/solana/programs/tbtc/src/state/mod.rs index abfdadb81..dffd4099b 100644 --- a/cross-chain/solana/programs/tbtc/src/state/mod.rs +++ b/cross-chain/solana/programs/tbtc/src/state/mod.rs @@ -1,7 +1,8 @@ +mod config; +pub use config::*; + +mod guardian_info; pub use guardian_info::*; -pub use minter_info::*; -pub use tbtc::*; -pub mod guardian_info; -pub mod minter_info; -pub mod tbtc; +mod minter_info; +pub use minter_info::*; diff --git a/cross-chain/solana/programs/tbtc/src/state/tbtc.rs b/cross-chain/solana/programs/tbtc/src/state/tbtc.rs deleted file mode 100644 index c5a3027b7..000000000 --- a/cross-chain/solana/programs/tbtc/src/state/tbtc.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Default)] -pub struct Tbtc { - pub authority: Pubkey, - pub token_mint: Pubkey, - pub token_bump: u8, - pub minters: u8, - pub guardians: u8, - pub paused: bool, -} - -impl Tbtc { - // 8 discriminator - // 32 pubkey - // 32 pubkey - // 1 u8 - // 1 u8 - // 1 u8 - // 1 bool - pub const MAXIMUM_SIZE: usize = 8 + 32 + 32 + 1 + 1 + 1 + 1; -} diff --git a/cross-chain/solana/programs/wormhole-gateway/Cargo.toml b/cross-chain/solana/programs/wormhole-gateway/Cargo.toml index 0df55d930..6df2961fc 100644 --- a/cross-chain/solana/programs/wormhole-gateway/Cargo.toml +++ b/cross-chain/solana/programs/wormhole-gateway/Cargo.toml @@ -9,13 +9,18 @@ crate-type = ["cdylib", "lib"] name = "wormhole_gateway" [features] +default = ["mainnet"] +mainnet = ["wormhole-anchor-sdk/mainnet"] +solana-devnet = ["wormhole-anchor-sdk/solana-devnet"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -default = [] [dependencies] -anchor-lang = { version="0.28.0", features = ["init-if-needed"]} +anchor-lang = { version = "0.28.0", features = ["init-if-needed"]} anchor-spl = "0.28.0" -tbtc = { path = "../tbtc" } \ No newline at end of file + +wormhole-anchor-sdk = { git = "https://github.com/wormhole-foundation/wormhole-scaffolding", rev = "f8d5ba04bfd449ab3693b15c818fd3e85e30f758", features = ["token-bridge"], default-features = false } + +tbtc = { path = "../tbtc", features = ["cpi"] } \ No newline at end of file diff --git a/cross-chain/solana/programs/wormhole-gateway/src/constants.rs b/cross-chain/solana/programs/wormhole-gateway/src/constants.rs new file mode 100644 index 000000000..7aea4b90d --- /dev/null +++ b/cross-chain/solana/programs/wormhole-gateway/src/constants.rs @@ -0,0 +1,14 @@ +pub const TBTC_FOREIGN_TOKEN_CHAIN: u8 = 2; + +#[cfg(feature = "mainnet")] +pub const TBTC_FOREIGN_TOKEN_ADDRESS: [u8; 32] = [ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8d, 0xae, 0xba, 0xde, 0x92, 0x2d, + 0xf7, 0x35, 0xc3, 0x8c, 0x80, 0xc7, 0xeb, 0xd7, 0x08, 0xaf, 0x50, 0x81, 0x5f, 0xaa, +]; + +/// TODO: Fix this to reflect testnet contract address. +#[cfg(feature = "solana-devnet")] +pub const TBTC_FOREIGN_TOKEN_ADDRESS: [u8; 32] = [ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8d, 0xae, 0xba, 0xde, 0x92, 0x2d, + 0xf7, 0x35, 0xc3, 0x8c, 0x80, 0xc7, 0xeb, 0xd7, 0x08, 0xaf, 0x50, 0x81, 0x5f, 0xaa, +]; diff --git a/cross-chain/solana/programs/wormhole-gateway/src/error.rs b/cross-chain/solana/programs/wormhole-gateway/src/error.rs index 9ebe15782..09a8b136c 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/error.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/error.rs @@ -2,6 +2,21 @@ use anchor_lang::prelude::error_code; #[error_code] pub enum WormholeGatewayError { - MintingLimitExceeded, - IsNotAuthority, + #[msg("Cannot mint more than the minting limit.")] + MintingLimitExceeded = 0x10, + + #[msg("Only custodian authority is permitted for this action.")] + IsNotAuthority = 0x20, + + #[msg("0x0 recipient not allowed")] + ZeroRecipient = 0x30, + + #[msg("Not enough wormhole tBTC in the gateway to bridge")] + NotEnoughWrappedTbtc = 0x40, + + #[msg("Amount must not be 0")] + ZeroAmount = 0x50, + + #[msg("Amount too low to bridge")] + TruncatedZeroAmount = 0x60, } diff --git a/cross-chain/solana/programs/wormhole-gateway/src/lib.rs b/cross-chain/solana/programs/wormhole-gateway/src/lib.rs index 4b3223473..3ee804192 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/lib.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/lib.rs @@ -1,3 +1,6 @@ +mod constants; +pub use constants::*; + pub mod error; mod processor; @@ -22,35 +25,31 @@ pub mod wormhole_gateway { pub fn update_gateway_address( ctx: Context, chain_id: u16, - gateway_address: [u8; 32] + gateway_address: [u8; 32], ) -> Result<()> { processor::update_gateway_address(ctx, chain_id, gateway_address) } - pub fn update_minting_limit( - ctx: Context, - new_limit: u64, - ) -> Result<()> { + pub fn update_minting_limit(ctx: Context, new_limit: u64) -> Result<()> { processor::update_minting_limit(ctx, new_limit) } - pub fn receive_tbtc( - ctx: Context, - ) -> Result<()> { - processor::receive_tbtc(ctx) - } + // pub fn receive_tbtc(ctx: Context) -> Result<()> { + // processor::receive_tbtc(ctx) + // } - pub fn send_tbtc( - ctx: Context, - amount: u64, + pub fn send_tbtc_to_gateway( + ctx: Context, recipient_chain: u16, + recipient: [u8; 32], + amount: u64, arbiter_fee: u64, nonce: u32, ) -> Result<()> { - processor::send_tbtc(ctx, amount, recipient_chain, arbiter_fee, nonce) + processor::send_tbtc_to_gateway(ctx, recipient_chain, recipient, amount, arbiter_fee, nonce) } pub fn deposit_wormhole_tbtc(ctx: Context, amount: u64) -> Result<()> { processor::deposit_wormhole_tbtc(ctx, amount) } -} \ No newline at end of file +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs index 0858c006f..24a263008 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/deposit_wormhole_tbtc.rs @@ -1,62 +1,103 @@ -use crate::{ - error::WormholeGatewayError, - state::WormholeGateway, -}; -use tbtc::{tbtc}; - +use crate::{error::WormholeGatewayError, state::Custodian}; use anchor_lang::prelude::*; use anchor_spl::token; #[derive(Accounts)] #[instruction(amount: u64)] pub struct DepositWormholeTbtc<'info> { + /// NOTE: This account also acts as a minter for the TBTC program. + #[account( + seeds = [Custodian::SEED_PREFIX], + bump = custodian.bump, + has_one = wrapped_tbtc_token, + has_one = wrapped_tbtc_mint, + has_one = tbtc_mint, + )] + custodian: Account<'info, Custodian>, + + /// This token account is owned by this program, whose mint is the wrapped TBTC mint. This PDA + /// address is stored in the custodian account. #[account(mut)] - pub tbtc_mint: Account<'info, token::Mint>, - pub tbtc: Account<'info, tbtc::Tbtc>, - pub minter_info: Account<'info, tbtc::MinterInfo>, + wrapped_tbtc_token: Account<'info, token::TokenAccount>, + + /// This mint is owned by the Wormhole Token Bridge program. This PDA address is stored in the + /// custodian account. + wrapped_tbtc_mint: Account<'info, token::Mint>, + + /// This mint is owned by the TBTC program. This PDA address is stored in the custodian account. + #[account(mut)] + tbtc_mint: Account<'info, token::Mint>, - // Use the associated token account for the recipient. #[account( - associated_token::mint = tbtc_mint, - associated_token::authority = recipient, + mut, + token::mint = wrapped_tbtc_mint, + token::authority = recipient )] - pub recipient_account: Account<'info, token::TokenAccount>, - pub recipient: Signer<'info>, + recipient_wrapped_token: Account<'info, token::TokenAccount>, + + // Use the associated token account for the recipient. #[account( - constraint = wormhole_gateway.minting_limit > wormhole_gateway.minted_amount + amount @ WormholeGatewayError::MintingLimitExceeded + mut, + token::mint = tbtc_mint, + token::authority = recipient, )] - pub wormhole_gateway: Account<'info, WormholeGateway>, + recipient_token: Account<'info, token::TokenAccount>, + + /// This program requires that the owner of the TBTC token account sign for TBTC being minted + /// into his account. + recipient: Signer<'info>, + + /// CHECK: TBTC program requires this account. + tbtc_config: UncheckedAccount<'info>, + + // CHECK: TBTC program requires this account. + minter_info: UncheckedAccount<'info>, + + token_program: Program<'info, token::Token>, + tbtc_program: Program<'info, tbtc::Tbtc>, } -pub fn deposit_wormhole_tbtc( - ctx: Context, - amount: u64, -) -> Result<()> { - ctx.accounts.wormhole_gateway.minted_amount += amount; - - let transfer_cpi_ctx = CpiContext::new_with_signer( - // - ); - // wormhole::transfer - - let seed_prefix = WormholeGateway::SEED_PREFIX; - let key_seed = ctx.accounts.wormhole_gateway.key(); - let gateway_bump = ctx.accounts.wormhole_gateway.self_bump; - - let signer: &[&[&[u8]]] = &[&[seed_prefix, key_seed.as_ref(), &[gateway_bump]]]; - - let mint_cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.tbtc.to_account_info(), - tbtc::Mint { - tbtc_mint: ctx.accounts.tbtc_mint.to_account_info(), - tbtc: ctx.accounts.tbtc.to_account_info(), - minter_info: ctx.accounts.minter_info.to_account_info(), - minter: ctx.accounts.wormhole_gateway.to_account_info(), - recipient_account: ctx.accounts.recipient_account.to_account_info(), - recipient: ctx.accounts.recipient.to_account_info(), - payer: ctx.accounts.payer.to_account_info(), - }, - signer, - ); - tbtc::mint(mint_cpi_ctx, amount); -} \ No newline at end of file +impl<'info> DepositWormholeTbtc<'info> { + fn constraints(ctx: &Context, amount: u64) -> Result<()> { + require_gt!( + ctx.accounts.custodian.minting_limit, + ctx.accounts.tbtc_mint.supply.saturating_add(amount), + WormholeGatewayError::MintingLimitExceeded + ); + + Ok(()) + } +} + +#[access_control(DepositWormholeTbtc::constraints(&ctx, amount))] +pub fn deposit_wormhole_tbtc(ctx: Context, amount: u64) -> Result<()> { + // First transfer wrapped tokens to custody account. + token::transfer( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + token::Transfer { + from: ctx.accounts.recipient_wrapped_token.to_account_info(), + to: ctx.accounts.wrapped_tbtc_token.to_account_info(), + authority: ctx.accounts.recipient.to_account_info(), + }, + ), + amount, + )?; + + // Now mint. + tbtc::cpi::mint( + CpiContext::new_with_signer( + ctx.accounts.tbtc_program.to_account_info(), + tbtc::cpi::accounts::Mint { + mint: ctx.accounts.tbtc_mint.to_account_info(), + config: ctx.accounts.tbtc_config.to_account_info(), + minter_info: ctx.accounts.minter_info.to_account_info(), + minter: ctx.accounts.custodian.to_account_info(), + recipient_token: ctx.accounts.recipient_token.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + }, + &[&[Custodian::SEED_PREFIX, &[ctx.bumps["custodian"]]]], + ), + amount, + ) +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs index c971f30fb..cf6afc196 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/initialize.rs @@ -1,46 +1,68 @@ use crate::{ - state::WormholeGateway, + constants::{TBTC_FOREIGN_TOKEN_ADDRESS, TBTC_FOREIGN_TOKEN_CHAIN}, + state::Custodian, }; - use anchor_lang::prelude::*; use anchor_spl::token; +use wormhole_anchor_sdk::token_bridge; #[derive(Accounts)] pub struct Initialize<'info> { #[account( - seeds = [SEED_PREFIX_TBTC_MINT, tbtc.key().as_ref()], + init, + payer = authority, + space = 8 + Custodian::INIT_SPACE, + seeds = [Custodian::SEED_PREFIX], bump, - mint::decimals = 9, - mint::authority = tbtc_mint, )] - pub tbtc_mint: Account<'info, token::Mint>, - pub tbtc: Account<'info, tbtc::Tbtc>, + custodian: Account<'info, Custodian>, + /// TBTC Program's mint PDA address bump is saved in this program's config. Ordinarily, we would + /// not have to deserialize this account. But we do in this case to make sure the TBTC program + /// has been initialized before this program. #[account( - init, payer = authority, space = WormholeGateway::MAXIMUM_SIZE + seeds = [tbtc::SEED_PREFIX_TBTC_MINT], + bump, + seeds::program = tbtc::ID )] - pub wormhole_gateway: Account<'info, WormholeGateway>, + tbtc_mint: Account<'info, token::Mint>, - pub wormhole_token_bridge: Account<'info, _>, - pub wormhole_bridge_token_mint: Account<'info, token::Mint>, + #[account( + seeds = [ + token_bridge::WrappedMint::SEED_PREFIX, + &TBTC_FOREIGN_TOKEN_CHAIN.to_be_bytes(), + TBTC_FOREIGN_TOKEN_ADDRESS.as_ref() + ], + bump + )] + wrapped_tbtc_mint: Account<'info, token::Mint>, + + #[account( + init, + payer = authority, + token::mint = wrapped_tbtc_mint, + token::authority = authority, + seeds = [b"wrapped-token"], + bump + )] + wrapped_tbtc_token: Account<'info, token::TokenAccount>, #[account(mut)] - pub authority: Signer<'info>, + authority: Signer<'info>, - pub token_program: Program<'info, token::Token>, - pub system_program: Program<'info, System>, + system_program: Program<'info, System>, + token_program: Program<'info, token::Token>, } pub fn initialize(ctx: Context, minting_limit: u64) -> Result<()> { - ctx.accounts.wormhole_gateway.set_inner(WormholeGateway { + ctx.accounts.custodian.set_inner(Custodian { + bump: ctx.bumps["config"], authority: ctx.accounts.authority.key(), - wormhole_token_bridge: ctx.accounts.wormhole_token_bridge.key(), - wormhole_bridge_token_mint: ctx.accounts.wormhole_bridge_token_mint.key(), - tbtc: ctx.accounts.tbtc.key(), tbtc_mint: ctx.accounts.tbtc_mint.key(), - minting_limit: minting_limit, - minted_amount: 0, - self_bump: ctx.bumps.get("wormhole_gateway").unwrap(), + wrapped_tbtc_mint: ctx.accounts.wrapped_tbtc_mint.key(), + wrapped_tbtc_token: ctx.accounts.wrapped_tbtc_token.key(), + minting_limit, }); + Ok(()) -} \ No newline at end of file +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/mod.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/mod.rs index 1481aefa2..d9cb203f3 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/mod.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/mod.rs @@ -4,14 +4,14 @@ pub use deposit_wormhole_tbtc::*; mod initialize; pub use initialize::*; -mod receive_tbtc; -pub use receive_tbtc::*; +// mod receive_tbtc; +// pub use receive_tbtc::*; -mod send_tbtc; -pub use send_tbtc::*; +mod send_tbtc_to_gateway; +pub use send_tbtc_to_gateway::*; mod update_gateway_address; pub use update_gateway_address::*; mod update_minting_limit; -pub use update_minting_limit::*; \ No newline at end of file +pub use update_minting_limit::*; diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs index cc9905c45..b3cc68d83 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/receive_tbtc.rs @@ -1,18 +1,31 @@ -use crate::{ - state::WormholeGateway, -}; - -use tbtc::{tbtc}; - +use crate::state::Custodian; use anchor_lang::prelude::*; use anchor_spl::token; #[derive(Accounts)] pub struct ReceiveTbtc<'info> { #[account(mut)] - pub tbtc_mint: Account<'info, token::Mint>, - pub tbtc: Account<'info, tbtc::Tbtc>, - pub minter_info: Account<'info, tbtc::MinterInfo>, + payer: Signer<'info>, + + /// NOTE: This account also acts as a minter for the TBTC program. + #[account( + seeds = [Custodian::SEED_PREFIX], + bump = custodian.bump, + has_one = wrapped_tbtc_token, + has_one = wrapped_tbtc_mint, + has_one = tbtc_mint, + )] + custodian: Account<'info, Custodian>, + + // TODO: posted_vaa + #[account(mut)] + tbtc_mint: Account<'info, token::Mint>, + + /// CHECK: This account is needed fot the TBTC program. + tbtc_config: UncheckedAccount<'info>, + + /// CHECK: This account is needed fot the TBTC program. + minter_info: UncheckedAccount<'info>, // Use the associated token account for the recipient. #[account( @@ -20,20 +33,14 @@ pub struct ReceiveTbtc<'info> { associated_token::authority = recipient, )] pub recipient_account: Account<'info, token::TokenAccount>, + /// CHECK: the recipient doesn't need to sign the mint, /// and it doesn't conform to any specific rules. /// Validating the recipient is the minter's responsibility. - pub recipient: UncheckedAccount<'info>, - - #[account(mut)] - pub payer: Signer<'info>, - - pub wormhole_gateway: Account<'info, WormholeGateway>, + recipient: AccountInfo<'info>, } -pub fn receive_tbtc( - ctx: Context, -) -> Result <()> { +pub fn receive_tbtc(ctx: Context) -> Result<()> { // get balance delta let amount = _; @@ -46,7 +53,7 @@ pub fn receive_tbtc( } else { ctx.accounts.wormhole_gateway.minted_amount += amount; - let seed_prefix = WormholeGateway::SEED_PREFIX; + let seed_prefix = Config::SEED_PREFIX; let key_seed = ctx.accounts.wormhole_gateway.key(); let gateway_bump = ctx.accounts.wormhole_gateway.self_bump; @@ -67,4 +74,4 @@ pub fn receive_tbtc( ); tbtc::mint(mint_cpi_ctx, amount) } -} \ No newline at end of file +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc.rs deleted file mode 100644 index dd4b971c8..000000000 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::{ - state::{GatewayInfo, WormholeGateway}, -}; - -use tbtc::{tbtc}; - -use anchor_lang::prelude::*; -use anchor_spl::token; - -#[derive(Accounts)] -#[instruction(recipient_chain: u16)] -pub struct SendTbtc<'info> { - #[account( - mut, - seeds = [tbtc::SEED_PREFIX_TBTC_MINT, tbtc.key().as_ref()], - bump, - mint::decimals = 9, - mint::authority = tbtc_mint, - )] - pub tbtc_mint: Account<'info, token::Mint>, - pub tbtc: Account<'info, tbtc::Tbtc>, - - pub wormhole_gateway: Account<'info, WormholeGateway>, - - pub wormhole_token_bridge: Account<'info, _>, - pub wormhole_bridge_token_mint: Account<'info, token::Mint>, - - #[account( - seeds = [GatewayInfo::SEED_PREFIX, wormhole_gateway.key().as_ref(), recipient_chain], - bump = gateway_info.bump, - )] - pub gateway_info: Account<'info, GatewayInfo>, - - pub sender_account: Account<'info, token::TokenAccount>, - pub sender: Signer<'info>, - - pub token_program: Program<'info, token::Token>, -} - -pub fn send_tbtc( - ctx: Context, - amount: u64, - recipient_chain: u16, - arbiter_fee: u64, - nonce: u32, -) -> Result<()> { - let normalized_amount = normalize(amount); - - let gateway = ctx.accounts.gateway_info.gateway; - - ctx.accounts.wormhole_gateway.minted_amount -= normalized_amount; - - let seed_prefix = WormholeGateway::SEED_PREFIX; - let key_seed = ctx.accounts.wormhole_gateway.key(); - let gateway_bump = ctx.accounts.wormhole_gateway.self_bump; - - let signer: &[&[&[u8]]] = &[&[seed_prefix, key_seed.as_ref(), &[gateway_bump]]]; - - let burn_cpi_ctx = CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), - token::Burn { - mint: ctx.accounts.tbtc_mint.to_account_info(), - from: ctx.accounts.sender_account.to_account_info(), - authority: ctx.accounts.wormhole_gateway.to_account_info(), - }, - signer, - ); - token::burn(burn_cpi_ctx, amount) - - - // approve bridge token - // transfer tokens -} - -fn normalize(amount: u64) -> u64 { - let divAmount = amount / 10; - let normAmount = divAmount * 10; - normAmount -} \ No newline at end of file diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc_to_gateway.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc_to_gateway.rs new file mode 100644 index 000000000..1b23b542d --- /dev/null +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/send_tbtc_to_gateway.rs @@ -0,0 +1,109 @@ +use crate::{ + error::WormholeGatewayError, + state::{Custodian, GatewayInfo}, +}; +use anchor_lang::prelude::*; +use anchor_spl::token; +use wormhole_anchor_sdk::token_bridge::{self, program::TokenBridge}; + +#[derive(Accounts)] +#[instruction(recipient_chain: u16)] +pub struct SendTbtcToGateway<'info> { + #[account( + seeds = [Custodian::SEED_PREFIX], + bump = custodian.bump, + has_one = wrapped_tbtc_mint, + has_one = tbtc_mint, + )] + custodian: Account<'info, Custodian>, + + #[account( + seeds = [GatewayInfo::SEED_PREFIX, &recipient_chain.to_le_bytes()], + bump = gateway_info.bump, + )] + gateway_info: Account<'info, GatewayInfo>, + + /// Custody account. + wrapped_tbtc_token: Account<'info, token::TokenAccount>, + + /// CHECK: This account is needed for the Token Bridge program. + wrapped_tbtc_mint: UncheckedAccount<'info>, + + #[account(mut)] + tbtc_mint: Account<'info, token::Mint>, + + #[account( + mut, + token::mint = wrapped_tbtc_mint, + token::authority = sender + )] + sender_account: Account<'info, token::TokenAccount>, + + sender: Signer<'info>, + + /// Check: This account is needed for the Token Bridge program. + token_bridge_transfer_authority: UncheckedAccount<'info>, + + token_bridge_program: Program<'info, TokenBridge>, + token_program: Program<'info, token::Token>, +} + +pub fn send_tbtc_to_gateway( + ctx: Context, + recipient_chain: u16, + recipient: [u8; 32], + amount: u64, + arbiter_fee: u64, + nonce: u32, +) -> Result<()> { + require!(recipient != [0; 32], WormholeGatewayError::ZeroRecipient); + require_gt!(amount, 0, WormholeGatewayError::ZeroAmount); + + let norm_amount = 10 * (amount / 10); + require_gt!(norm_amount, 0, WormholeGatewayError::TruncatedZeroAmount); + + let gateway = ctx.accounts.gateway_info.gateway; + + ctx.accounts + .wrapped_tbtc_token + .amount + .checked_sub(norm_amount) + .ok_or_else(|| WormholeGatewayError::NotEnoughWrappedTbtc); + + let token_program = &ctx.accounts.token_program; + + // Burn TBTC mint. + token::burn( + CpiContext::new( + token_program.to_account_info(), + token::Burn { + mint: ctx.accounts.tbtc_mint.to_account_info(), + from: ctx.accounts.sender_account.to_account_info(), + authority: ctx.accounts.sender.to_account_info(), + }, + ), + amount, + )?; + + // Delegate authority to Token Bridge's transfer authority. + token::approve( + CpiContext::new_with_signer( + token_program.to_account_info(), + token::Approve { + to: ctx.accounts.wrapped_tbtc_token.to_account_info(), + delegate: ctx + .accounts + .token_bridge_transfer_authority + .to_account_info(), + authority: ctx.accounts.custodian.to_account_info(), + }, + &[&[Custodian::SEED_PREFIX, &[ctx.accounts.custodian.bump]]], + ), + amount, + )?; + + // TODO: Encode message with recipient. + // TODO: Transfer tokens with message. + + Ok(()) +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/update_gateway_address.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/update_gateway_address.rs index e0f3ef36d..25d8358e4 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/update_gateway_address.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/update_gateway_address.rs @@ -1,6 +1,6 @@ use crate::{ error::WormholeGatewayError, - state::{GatewayInfo, WormholeGateway}, + state::{Custodian, GatewayInfo}, }; use anchor_lang::prelude::*; @@ -8,20 +8,23 @@ use anchor_lang::prelude::*; #[instruction(chain_id: u16)] pub struct UpdateGatewayAddress<'info> { #[account( + seeds = [Custodian::SEED_PREFIX], + bump = custodian.bump, has_one = authority @ WormholeGatewayError::IsNotAuthority, )] - pub wormhole_gateway: Account<'info, WormholeGateway>, + pub custodian: Account<'info, Custodian>, + #[account( init_if_needed, - payer = payer, - space = GatewayInfo::MAXIMUM_SIZE, - seeds = [GatewayInfo::SEED_PREFIX, wormhole_gateway.key().as_ref(), chain_id], + payer = authority, + space = 8 + GatewayInfo::INIT_SPACE, + seeds = [GatewayInfo::SEED_PREFIX, &chain_id.to_le_bytes()], bump, )] pub gateway_info: Account<'info, GatewayInfo>, - pub authority: Signer<'info>, + #[account(mut)] - pub payer: Signer<'info>, + pub authority: Signer<'info>, pub system_program: Program<'info, System>, } @@ -29,8 +32,12 @@ pub struct UpdateGatewayAddress<'info> { pub fn update_gateway_address( ctx: Context, chain_id: u16, - gateway_address: [u8; 32], + gateway: [u8; 32], ) -> Result<()> { - ctx.accounts.gateway_info.gateway = gateway_address; + ctx.accounts.gateway_info.set_inner(GatewayInfo { + bump: ctx.bumps["gateway_info"], + gateway, + }); + Ok(()) -} \ No newline at end of file +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/processor/update_minting_limit.rs b/cross-chain/solana/programs/wormhole-gateway/src/processor/update_minting_limit.rs index 3553c818d..b52b72df4 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/processor/update_minting_limit.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/processor/update_minting_limit.rs @@ -1,22 +1,20 @@ -use crate::{ - error::WormholeGatewayError, - state::WormholeGateway, -}; +use crate::{error::WormholeGatewayError, state::Custodian}; use anchor_lang::prelude::*; #[derive(Accounts)] pub struct UpdateMintingLimit<'info> { #[account( mut, + seeds = [Custodian::SEED_PREFIX], + bump = custodian.bump, has_one = authority @ WormholeGatewayError::IsNotAuthority, )] - pub wormhole_gateway: Account<'info, WormholeGateway>, - pub authority: Signer<'info>, - #[account(mut)] - pub payer: Signer<'info>, + custodian: Account<'info, Custodian>, + + authority: Signer<'info>, } pub fn update_minting_limit(ctx: Context, new_limit: u64) -> Result<()> { - ctx.accounts.wormhole_gateway.minting_limit = new_limit; + ctx.accounts.custodian.minting_limit = new_limit; Ok(()) -} \ No newline at end of file +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs b/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs new file mode 100644 index 000000000..fd0640913 --- /dev/null +++ b/cross-chain/solana/programs/wormhole-gateway/src/state/custodian.rs @@ -0,0 +1,18 @@ +use anchor_lang::prelude::*; + +#[account] +#[derive(Debug, InitSpace)] +pub struct Custodian { + pub bump: u8, + pub authority: Pubkey, + + pub tbtc_mint: Pubkey, + pub wrapped_tbtc_mint: Pubkey, + pub wrapped_tbtc_token: Pubkey, + + pub minting_limit: u64, +} + +impl Custodian { + pub const SEED_PREFIX: &'static [u8] = b"custodian"; +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/state/gateway_info.rs b/cross-chain/solana/programs/wormhole-gateway/src/state/gateway_info.rs index 8e0867d3a..895343c8a 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/state/gateway_info.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/state/gateway_info.rs @@ -1,13 +1,12 @@ use anchor_lang::prelude::*; #[account] -#[derive(Default)] +#[derive(Debug, InitSpace)] pub struct GatewayInfo { - pub gateway: [u8; 32], pub bump: u8, + pub gateway: [u8; 32], } impl GatewayInfo { - pub const MAXIMUM_SIZE: usize = 8 + 32 + 1; - pub const SEED_PREFIX: &'static [u8; 12] = b"gateway-info"; -} \ No newline at end of file + pub const SEED_PREFIX: &'static [u8] = b"gateway-info"; +} diff --git a/cross-chain/solana/programs/wormhole-gateway/src/state/mod.rs b/cross-chain/solana/programs/wormhole-gateway/src/state/mod.rs index b7f0583ab..8b8779a6a 100644 --- a/cross-chain/solana/programs/wormhole-gateway/src/state/mod.rs +++ b/cross-chain/solana/programs/wormhole-gateway/src/state/mod.rs @@ -1,5 +1,5 @@ -pub use gateway_info::*; -pub use wormhole_gateway::*; +mod custodian; +pub use custodian::*; -pub mod gateway_info; -pub mod wormhole_gateway; \ No newline at end of file +mod gateway_info; +pub use gateway_info::*; diff --git a/cross-chain/solana/programs/wormhole-gateway/src/state/wormhole_gateway.rs b/cross-chain/solana/programs/wormhole-gateway/src/state/wormhole_gateway.rs deleted file mode 100644 index 887f7f01e..000000000 --- a/cross-chain/solana/programs/wormhole-gateway/src/state/wormhole_gateway.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anchor_lang::prelude::*; - -#[account] -#[derive(Default)] -pub struct WormholeGateway { - pub authority: Pubkey, - pub wormhole_token_bridge: Pubkey, - pub wormhole_bridge_token_mint: Pubkey, - pub tbtc: Pubkey, - pub tbtc_mint: Pubkey, - pub minting_limit: u64, - pub minted_amount: u64, - pub self_bump: u8, -} - -impl WormholeGateway { - // 8 discriminator - // 32 * 5 = 160 (5 pubkeys) - // 8 * 2 = 16 (2 u64s) - // 1 (bump) - pub const MAXIMUM_SIZE: usize = 8 + 160 + 16 + 1; -} \ No newline at end of file diff --git a/cross-chain/solana/tests/01__tbtc.ts b/cross-chain/solana/tests/01__tbtc.ts index f5976b7e1..fad8af5da 100644 --- a/cross-chain/solana/tests/01__tbtc.ts +++ b/cross-chain/solana/tests/01__tbtc.ts @@ -4,6 +4,7 @@ import * as spl from "@solana/spl-token"; import * as web3 from '@solana/web3.js'; import { Tbtc } from "../target/types/tbtc"; import { expect } from 'chai'; +import { ASSOCIATED_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token"; function maybeAuthorityAnd( signer, @@ -14,53 +15,52 @@ function maybeAuthorityAnd( async function setup( program: Program, - tbtc, authority ) { - const [tbtcMintPDA, _] = getTokenPDA(program, tbtc); - + const [config,] = getConfigPDA(program); + const [tbtcMintPDA, _] = getTokenPDA(program); + await program.methods .initialize() .accounts({ - tbtcMint: tbtcMintPDA, - tbtc: tbtc.publicKey, + mint: tbtcMintPDA, + config, authority: authority.publicKey }) - .signers([tbtc]) .rpc(); } async function checkState( program: Program, - tbtc, expectedAuthority, expectedMinters, expectedGuardians, expectedTokensSupply ) { - let tbtcState = await program.account.tbtc.fetch(tbtc.publicKey); + const [config,] = getConfigPDA(program); + let configState = await program.account.config.fetch(config); - expect(tbtcState.authority).to.eql(expectedAuthority.publicKey); - expect(tbtcState.minters).to.equal(expectedMinters); - expect(tbtcState.guardians).to.equal(expectedGuardians); + expect(configState.authority).to.eql(expectedAuthority.publicKey); + expect(configState.numMinters).to.equal(expectedMinters); + expect(configState.numGuardians).to.equal(expectedGuardians); - let tbtcMint = tbtcState.tokenMint; + let tbtcMint = configState.mint; let mintState = await spl.getMint(program.provider.connection, tbtcMint); - expect(mintState.supply == expectedTokensSupply).to.be.true; + expect(mintState.supply).to.equal(BigInt(expectedTokensSupply)); } async function changeAuthority( program: Program, - tbtc, authority, newAuthority, ) { + const [config,] = getConfigPDA(program); await program.methods .changeAuthority() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey, newAuthority: newAuthority.publicKey, }) @@ -70,21 +70,31 @@ async function changeAuthority( async function checkPaused( program: Program, - tbtc, paused: boolean ) { - let tbtcState = await program.account.tbtc.fetch(tbtc.publicKey); - expect(tbtcState.paused).to.equal(paused); + const [config,] = getConfigPDA(program); + let configState = await program.account.config.fetch(config); + expect(configState.paused).to.equal(paused); +} + + +function getConfigPDA( + program: Program, +): [anchor.web3.PublicKey, number] { + return web3.PublicKey.findProgramAddressSync( + [ + Buffer.from('config'), + ], + program.programId + ); } function getTokenPDA( program: Program, - tbtc, ): [anchor.web3.PublicKey, number] { return web3.PublicKey.findProgramAddressSync( [ - anchor.utils.bytes.utf8.encode('tbtc-mint'), - tbtc.publicKey.toBuffer(), + Buffer.from('tbtc-mint'), ], program.programId ); @@ -92,13 +102,11 @@ function getTokenPDA( function getMinterPDA( program: Program, - tbtc, minter ): [anchor.web3.PublicKey, number] { return web3.PublicKey.findProgramAddressSync( [ - anchor.utils.bytes.utf8.encode('minter-info'), - tbtc.publicKey.toBuffer(), + Buffer.from('minter-info'), minter.publicKey.toBuffer(), ], program.programId @@ -107,19 +115,18 @@ function getMinterPDA( async function addMinter( program: Program, - tbtc, authority, minter, payer ): Promise { - const [minterInfoPDA, _] = getMinterPDA(program, tbtc, minter); + const [config,] = getConfigPDA(program); + const [minterInfoPDA, _] = getMinterPDA(program, minter); await program.methods .addMinter() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey, minter: minter.publicKey, - payer: payer.publicKey, minterInfo: minterInfoPDA, }) .signers(maybeAuthorityAnd(authority, [])) @@ -129,10 +136,9 @@ async function addMinter( async function checkMinter( program: Program, - tbtc, minter ) { - const [minterInfoPDA, bump] = getMinterPDA(program, tbtc, minter); + const [minterInfoPDA, bump] = getMinterPDA(program, minter); let minterInfo = await program.account.minterInfo.fetch(minterInfoPDA); expect(minterInfo.minter).to.eql(minter.publicKey); @@ -141,17 +147,18 @@ async function checkMinter( async function removeMinter( program: Program, - tbtc, authority, minter, minterInfo ) { + const [config,] = getConfigPDA(program); await program.methods - .removeMinter(minter.publicKey) + .removeMinter() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey, minterInfo: minterInfo, + minter: minter.publicKey }) .signers(maybeAuthorityAnd(authority, [])) .rpc(); @@ -159,13 +166,11 @@ async function removeMinter( function getGuardianPDA( program: Program, - tbtc, guardian ): [anchor.web3.PublicKey, number] { return web3.PublicKey.findProgramAddressSync( [ - anchor.utils.bytes.utf8.encode('guardian-info'), - tbtc.publicKey.toBuffer(), + Buffer.from('guardian-info'), guardian.publicKey.toBuffer(), ], program.programId @@ -174,20 +179,19 @@ function getGuardianPDA( async function addGuardian( program: Program, - tbtc, authority, guardian, payer ): Promise { - const [guardianInfoPDA, _] = getGuardianPDA(program, tbtc, guardian); + const [config,] = getConfigPDA(program); + const [guardianInfoPDA, _] = getGuardianPDA(program, guardian); await program.methods .addGuardian() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey, - guardian: guardian.publicKey, - payer: payer.publicKey, guardianInfo: guardianInfoPDA, + guardian: guardian.publicKey, }) .signers(maybeAuthorityAnd(authority, [])) .rpc(); @@ -196,10 +200,9 @@ async function addGuardian( async function checkGuardian( program: Program, - tbtc, guardian ) { - const [guardianInfoPDA, bump] = getGuardianPDA(program, tbtc, guardian); + const [guardianInfoPDA, bump] = getGuardianPDA(program, guardian); let guardianInfo = await program.account.guardianInfo.fetch(guardianInfoPDA); expect(guardianInfo.guardian).to.eql(guardian.publicKey); @@ -208,17 +211,18 @@ async function checkGuardian( async function removeGuardian( program: Program, - tbtc, authority, guardian, guardianInfo ) { + const [config,] = getConfigPDA(program); await program.methods - .removeGuardian(guardian.publicKey) + .removeGuardian() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey, guardianInfo: guardianInfo, + guardian: guardian.publicKey }) .signers(maybeAuthorityAnd(authority, [])) .rpc(); @@ -226,14 +230,14 @@ async function removeGuardian( async function pause( program: Program, - tbtc, guardian ) { - const [guardianInfoPDA, _] = getGuardianPDA(program, tbtc, guardian); + const [config,] = getConfigPDA(program); + const [guardianInfoPDA, _] = getGuardianPDA(program, guardian); await program.methods .pause() .accounts({ - tbtc: tbtc.publicKey, + config, guardianInfo: guardianInfoPDA, guardian: guardian.publicKey }) @@ -243,13 +247,13 @@ async function pause( async function unpause( program: Program, - tbtc, authority ) { + const [config,] = getConfigPDA(program); await program.methods .unpause() .accounts({ - tbtc: tbtc.publicKey, + config, authority: authority.publicKey }) .signers(maybeAuthorityAnd(authority, [])) @@ -258,29 +262,50 @@ async function unpause( async function mint( program: Program, - tbtc, minter, minterInfoPDA, recipient, amount, payer, ) { - const [tbtcMintPDA, _] = getTokenPDA(program, tbtc); - const associatedTokenAccount = spl.getAssociatedTokenAddressSync( - tbtcMintPDA, - recipient.publicKey, - ); + const connection = program.provider.connection; + + const [config,] = getConfigPDA(program); + const [tbtcMintPDA, _] = getTokenPDA(program); + const recipientToken = spl.getAssociatedTokenAddressSync(tbtcMintPDA, recipient.publicKey); + + const tokenData = await spl.getAccount(connection, recipientToken).catch((err) => { + if (err instanceof spl.TokenAccountNotFoundError) { + return null; + } else { + throw err; + }; + }); + + if (tokenData === null) { + const tx = await web3.sendAndConfirmTransaction( + connection, + new web3.Transaction().add( + spl.createAssociatedTokenAccountIdempotentInstruction( + payer.publicKey, + recipientToken, + recipient.publicKey, + tbtcMintPDA, + ) + ), + [payer.payer] + ); + } + await program.methods .mint(new anchor.BN(amount)) .accounts({ - tbtcMint: tbtcMintPDA, - tbtc: tbtc.publicKey, + mint: tbtcMintPDA, + config, minterInfo: minterInfoPDA, minter: minter.publicKey, - recipientAccount: associatedTokenAccount, - recipient: recipient.publicKey, - payer: payer.publicKey, + recipientToken, }) .signers(maybeAuthorityAnd(payer, [minter])) .rpc(); @@ -292,8 +317,7 @@ describe("tbtc", () => { const program = anchor.workspace.Tbtc as Program; - - const authority = (program.provider as anchor.AnchorProvider).wallet; + const authority = (program.provider as anchor.AnchorProvider).wallet as anchor.Wallet; const newAuthority = anchor.web3.Keypair.generate(); const minterKeys = anchor.web3.Keypair.generate(); const minter2Keys = anchor.web3.Keypair.generate(); @@ -304,29 +328,39 @@ describe("tbtc", () => { const recipientKeys = anchor.web3.Keypair.generate(); it('setup', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); + await setup(program, authority); + await checkState(program, authority, 0, 0, 0); }); it('change authority', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - await changeAuthority(program, tbtcKeys, authority, newAuthority); - await checkState(program, tbtcKeys, newAuthority, 0, 0, 0); + await checkState(program, authority, 0, 0, 0); + await changeAuthority(program, authority, newAuthority); + await checkState(program, newAuthority, 0, 0, 0); + await changeAuthority(program, newAuthority, authority.payer); + await checkState(program, authority, 0, 0, 0); }) it('add minter', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkState(program, tbtcKeys, authority, 1, 0, 0); + await checkState(program, authority, 0, 0, 0); + await addMinter(program, authority, minterKeys, authority); + await checkMinter(program, minterKeys); + await checkState(program, authority, 1, 0, 0); + + // Transfer lamports to imposter. + await web3.sendAndConfirmTransaction( + program.provider.connection, + new web3.Transaction().add( + web3.SystemProgram.transfer({ + fromPubkey: authority.publicKey, + toPubkey: impostorKeys.publicKey, + lamports: 1000000000, + }) + ), + [authority.payer] + ); try { - await addMinter(program, tbtcKeys, impostorKeys, minter2Keys, authority); + await addMinter(program, impostorKeys, minter2Keys, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -337,31 +371,35 @@ describe("tbtc", () => { }); it('mint', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkState(program, tbtcKeys, authority, 1, 0, 0); + await checkState(program, authority, 1, 0, 0); + const [minterInfoPDA, _] = getMinterPDA(program, minterKeys); + await checkMinter(program, minterKeys); - // await setupMint(program, tbtcKeys, authority, recipientKeys); - await mint(program, tbtcKeys, minterKeys, minterInfoPDA, recipientKeys, 1000, authority); + // await setupMint(program, authority, recipientKeys); + await mint(program, minterKeys, minterInfoPDA, recipientKeys, 1000, authority); + + await checkState(program, authority, 1, 0, 1000); + + // // Burn for next test. + // const ix = spl.createBurnCheckedInstruction( + // account, // PublicKey of Owner's Associated Token Account + // new PublicKey(MINT_ADDRESS), // Public Key of the Token Mint Address + // WALLET.publicKey, // Public Key of Owner's Wallet + // BURN_QUANTITY * (10**MINT_DECIMALS), // Number of tokens to burn + // MINT_DECIMALS // Number of Decimals of the Token Mint + // ) - await checkState(program, tbtcKeys, authority, 1, 0, 1000); }); it('won\'t mint', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkState(program, tbtcKeys, authority, 1, 0, 0); + await checkState(program, authority, 1, 0, 1000); + const [minterInfoPDA, _] = getMinterPDA(program, minterKeys); + await checkMinter(program, minterKeys); - // await setupMint(program, tbtcKeys, authority, recipientKeys); + // await setupMint(program, authority, recipientKeys); try { - await mint(program, tbtcKeys, impostorKeys, minterInfoPDA, recipientKeys, 1000, authority); + await mint(program, impostorKeys, minterInfoPDA, recipientKeys, 1000, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -372,19 +410,17 @@ describe("tbtc", () => { }); it('use two minters', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - const minter2InfoPDA = await addMinter(program, tbtcKeys, authority, minter2Keys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkMinter(program, tbtcKeys, minter2Keys); - await checkState(program, tbtcKeys, authority, 2, 0, 0); - // await setupMint(program, tbtcKeys, authority, recipientKeys); + await checkState(program, authority, 1, 0, 1000); + const [minterInfoPDA, _] = getMinterPDA(program, minterKeys); + await checkMinter(program, minterKeys); + const minter2InfoPDA = await addMinter(program, authority, minter2Keys, authority); + await checkMinter(program, minter2Keys); + await checkState(program, authority, 2, 0, 1000); + // await setupMint(program, authority, recipientKeys); // cannot mint with wrong keys try { - await mint(program, tbtcKeys, minter2Keys, minterInfoPDA, recipientKeys, 1000, authority); + await mint(program, minter2Keys, minterInfoPDA, recipientKeys, 1000, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -395,7 +431,7 @@ describe("tbtc", () => { // cannot remove minter with wrong keys try { - await removeMinter(program, tbtcKeys, authority, minter2Keys, minterInfoPDA); + await removeMinter(program, authority, minter2Keys, minterInfoPDA); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -404,31 +440,26 @@ describe("tbtc", () => { expect(err.program.equals(program.programId)).is.true; } - await mint(program, tbtcKeys, minterKeys, minterInfoPDA, recipientKeys, 500, authority); - await checkState(program, tbtcKeys, authority, 2, 0, 500); + await mint(program, minterKeys, minterInfoPDA, recipientKeys, 500, authority); + await checkState(program, authority, 2, 0, 1500); }); it('remove minter', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkState(program, tbtcKeys, authority, 1, 0, 0); - await removeMinter(program, tbtcKeys, authority, minterKeys, minterInfoPDA); - await checkState(program, tbtcKeys, authority, 0, 0, 0); + await checkState(program, authority, 2, 0, 1500); + const [minter2InfoPDA, _] = getMinterPDA(program, minter2Keys); + await checkMinter(program, minter2Keys); + await removeMinter(program, authority, minter2Keys, minter2InfoPDA); + await checkState(program, authority, 1, 0, 1500); }); it('won\'t remove minter', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await checkMinter(program, tbtcKeys, minterKeys); - await checkState(program, tbtcKeys, authority, 1, 0, 0); + await checkState(program, authority, 1, 0, 1500); + const [minterInfoPDA, _] = getMinterPDA(program, minterKeys); + await checkMinter(program, minterKeys); try { - await removeMinter(program, tbtcKeys, impostorKeys, minterKeys, minterInfoPDA); + await removeMinter(program, impostorKeys, minterKeys, minterInfoPDA); + chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); const err: AnchorError = _err; @@ -436,11 +467,12 @@ describe("tbtc", () => { expect(err.program.equals(program.programId)).is.true; } - await removeMinter(program, tbtcKeys, authority, minterKeys, minterInfoPDA); - await checkState(program, tbtcKeys, authority, 0, 0, 0); + await removeMinter(program, authority, minterKeys, minterInfoPDA); + await checkState(program, authority, 0, 0, 1500); try { - await removeMinter(program, tbtcKeys, authority, minterKeys, minterInfoPDA); + await removeMinter(program, authority, minterKeys, minterInfoPDA); + chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); const err: AnchorError = _err; @@ -450,15 +482,13 @@ describe("tbtc", () => { }); it('add guardian', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await checkGuardian(program, tbtcKeys, guardianKeys); - await checkState(program, tbtcKeys, authority, 0, 1, 0); + await checkState(program, authority, 0, 0, 1500); + await addGuardian(program, authority, guardianKeys, authority); + await checkGuardian(program, guardianKeys); + await checkState(program, authority, 0, 1, 1500); try { - await addGuardian(program, tbtcKeys, impostorKeys, guardian2Keys, authority); + await addGuardian(program, impostorKeys, guardian2Keys, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -469,15 +499,12 @@ describe("tbtc", () => { }); it('remove guardian', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const guardianInfoPDA = await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await checkGuardian(program, tbtcKeys, guardianKeys); - await checkState(program, tbtcKeys, authority, 0, 1, 0); + await checkState(program, authority, 0, 1, 1500); + const [guardianInfoPDA, _] = getGuardianPDA(program, guardianKeys); + await checkGuardian(program, guardianKeys); try { - await removeGuardian(program, tbtcKeys, impostorKeys, guardianKeys, guardianInfoPDA); + await removeGuardian(program, impostorKeys, guardianKeys, guardianInfoPDA); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -486,11 +513,11 @@ describe("tbtc", () => { expect(err.program.equals(program.programId)).is.true; } - await removeGuardian(program, tbtcKeys, authority, guardianKeys, guardianInfoPDA); - await checkState(program, tbtcKeys, authority, 0, 0, 0); + await removeGuardian(program, authority, guardianKeys, guardianInfoPDA); + await checkState(program, authority, 0, 0, 1500); try { - await removeGuardian(program, tbtcKeys, authority, guardianKeys, guardianInfoPDA); + await removeGuardian(program, authority, guardianKeys, guardianInfoPDA); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -501,26 +528,21 @@ describe("tbtc", () => { }); it('pause', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await checkPaused(program, tbtcKeys, false); - await pause(program, tbtcKeys, guardianKeys); - await checkPaused(program, tbtcKeys, true); + await checkState(program, authority, 0, 0, 1500); + await addGuardian(program, authority, guardianKeys, authority); + await checkPaused(program, false); + await pause(program, guardianKeys); + await checkPaused(program, true); }); it('unpause', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await checkPaused(program, tbtcKeys, false); - await pause(program, tbtcKeys, guardianKeys); - await checkPaused(program, tbtcKeys, true); - await unpause(program, tbtcKeys, authority); - await checkPaused(program, tbtcKeys, false); + await checkState(program, authority, 0, 1, 1500); + await checkPaused(program, true); + await unpause(program, authority); + await checkPaused(program, false); try { - await unpause(program, tbtcKeys, authority); + await unpause(program, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { @@ -532,16 +554,13 @@ describe("tbtc", () => { }); it('won\'t mint when paused', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - const minterInfoPDA = await addMinter(program, tbtcKeys, authority, minterKeys, authority); - await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await pause(program, tbtcKeys, guardianKeys); - // await setupMint(program, tbtcKeys, authority, recipientKeys); + await checkState(program, authority, 0, 1, 1500); + const minterInfoPDA = await addMinter(program, authority, minterKeys, authority); + await pause(program, guardianKeys); + // await setupMint(program, authority, recipientKeys); try { - await mint(program, tbtcKeys, minterKeys, minterInfoPDA, recipientKeys, 1000, authority); - + await mint(program, minterKeys, minterInfoPDA, recipientKeys, 1000, authority); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -549,22 +568,22 @@ describe("tbtc", () => { expect(err.error.errorCode.code).to.equal('IsPaused'); expect(err.program.equals(program.programId)).is.true; } + + await unpause(program, authority); + await checkPaused(program, false); }) it('use two guardians', async () => { - const tbtcKeys = anchor.web3.Keypair.generate(); - await setup(program, tbtcKeys, authority); - await checkState(program, tbtcKeys, authority, 0, 0, 0); - const guardianInfoPDA = await addGuardian(program, tbtcKeys, authority, guardianKeys, authority); - await addGuardian(program, tbtcKeys, authority, guardian2Keys, authority); - await checkGuardian(program, tbtcKeys, guardianKeys); - await checkGuardian(program, tbtcKeys, guardian2Keys); - await checkState(program, tbtcKeys, authority, 0, 2, 0); + await checkState(program, authority, 1, 1, 1500); + const [guardianInfoPDA, _] = getGuardianPDA(program, guardianKeys); + await checkGuardian(program, guardianKeys); + await addGuardian(program, authority, guardian2Keys, authority); + await checkGuardian(program, guardian2Keys); - await pause(program, tbtcKeys, guardianKeys); + await pause(program, guardianKeys); try { - await pause(program, tbtcKeys, guardian2Keys); + await pause(program, guardian2Keys); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError); @@ -573,14 +592,14 @@ describe("tbtc", () => { expect(err.program.equals(program.programId)).is.true; } - await unpause(program, tbtcKeys, authority); - await pause(program, tbtcKeys, guardian2Keys); - await checkPaused(program, tbtcKeys, true); - await unpause(program, tbtcKeys, authority); + await unpause(program, authority); + await pause(program, guardian2Keys); + await checkPaused(program, true); + await unpause(program, authority); // cannot remove guardian with wrong keys try { - await removeGuardian(program, tbtcKeys, authority, guardian2Keys, guardianInfoPDA); + await removeGuardian(program, authority, guardian2Keys, guardianInfoPDA); chai.assert(false, "should've failed but didn't"); } catch (_err) { expect(_err).to.be.instanceOf(AnchorError);