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 5accd9bc8..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::*; 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 afa376f8a..239635a62 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/pause.rs @@ -10,7 +10,6 @@ pub struct Pause<'info> { mut, seeds = [Config::SEED_PREFIX], bump, - constraint = !config.paused @ TbtcError::IsPaused )] config: Account<'info, Config>, @@ -24,6 +23,15 @@ pub struct Pause<'info> { 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.config.paused = true; 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 024a20685..e5ce8fa25 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/admin/unpause.rs @@ -5,16 +5,24 @@ use anchor_lang::prelude::*; pub struct Unpause<'info> { #[account( mut, + has_one = authority @ TbtcError::IsNotAuthority, seeds = [Config::SEED_PREFIX], bump, - has_one = authority @ TbtcError::IsNotAuthority, - constraint = config.paused @ TbtcError::IsNotPaused )] 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.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 c32ab15e5..f64307754 100644 --- a/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs +++ b/cross-chain/solana/programs/tbtc/src/processor/mint/mint.rs @@ -17,9 +17,9 @@ pub struct Mint<'info> { )] mint: Account<'info, token::Mint>, - // Can not mint when paused. #[account( - constraint = !config.paused @ TbtcError::IsPaused + seeds = [Config::SEED_PREFIX], + bump = config.bump, )] config: Account<'info, Config>, @@ -30,6 +30,7 @@ pub struct Mint<'info> { bump = minter_info.bump, )] minter_info: Account<'info, MinterInfo>, + minter: Signer<'info>, // Use the associated token account for the recipient. @@ -40,9 +41,18 @@ pub struct Mint<'info> { recipient_token: Account<'info, token::TokenAccount>, token_program: Program<'info, token::Token>, - 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<()> { token::mint_to( CpiContext::new_with_signer( 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 00364eac5..34f1492ba 100644 --- a/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs +++ b/cross-chain/solana/programs/tbtc/src/state/guardian_info.rs @@ -8,5 +8,5 @@ pub struct GuardianInfo { } impl GuardianInfo { - 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/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