diff --git a/Cargo.lock b/Cargo.lock index e99a5675..610d4021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "anchor-spl" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea94b04fc9a0aaae4d4473b0595fb5f55b6c9b38e0d6f596df8c8060f95f096" +dependencies = [ + "anchor-lang", + "solana-program", + "spl-associated-token-account", + "spl-token", +] + [[package]] name = "anchor-syn" version = "0.20.1" @@ -1682,6 +1694,16 @@ dependencies = [ "syn", ] +[[package]] +name = "spl-associated-token-account" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "393e2240d521c3dd770806bff25c2c00d761ac962be106e14e22dd912007f428" +dependencies = [ + "solana-program", + "spl-token", +] + [[package]] name = "spl-token" version = "3.3.0" @@ -1700,11 +1722,14 @@ dependencies = [ name = "stable-swap" version = "1.6.7" dependencies = [ + "anchor-lang", + "anchor-spl", "solana-program", "solana-sdk", "spl-token", "stable-swap-client", "stable-swap-math", + "vipers", ] [[package]] @@ -1911,6 +1936,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vipers" +version = "1.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8c839e2a15777b45909d05ab3eeaaa3035b43241b404345c9cd999ed30df09" +dependencies = [ + "anchor-lang", + "anchor-spl", + "spl-associated-token-account", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/stable-swap-program/program/Cargo.toml b/stable-swap-program/program/Cargo.toml index 6e1821be..81a6fed9 100644 --- a/stable-swap-program/program/Cargo.toml +++ b/stable-swap-program/program/Cargo.toml @@ -15,10 +15,13 @@ no-entrypoint = [] fuzz = ["stable-swap-client/fuzz"] [dependencies] +anchor-lang = ">=0.17" +anchor-spl = ">=0.17" solana-program = "^1.6.10" spl-token = { version = "3.3.0", features = ["no-entrypoint"] } stable-swap-client = { path = "../../stable-swap-client", version = "^1" } stable-swap-math = { path = "../../stable-swap-math", version = "^1" } +vipers = "^1.5.5" [dev-dependencies] solana-sdk = "^1.9.5" diff --git a/stable-swap-program/program/src/lib.rs b/stable-swap-program/program/src/lib.rs index 3bbb3963..8bf2db62 100644 --- a/stable-swap-program/program/src/lib.rs +++ b/stable-swap-program/program/src/lib.rs @@ -11,6 +11,8 @@ pub mod processor; pub use stable_swap_client::{error, fees, instruction, state}; pub use stable_swap_math::{curve, math, pool_converter}; +pub use stable_swap_client::error::SwapError as ErrorCode; + /// Export current solana-program types for downstream users who may also be /// building with a different solana-program version pub use solana_program; diff --git a/stable-swap-program/program/src/processor/checks.rs b/stable-swap-program/program/src/processor/checks.rs index e1ffb482..fcc6652a 100644 --- a/stable-swap-program/program/src/processor/checks.rs +++ b/stable-swap-program/program/src/processor/checks.rs @@ -1,27 +1,43 @@ //! Checks for processing instructions. +use anchor_lang::prelude::*; + +use super::logging::log_slippage_error; use crate::{ error::SwapError, processor::utils, state::{SwapInfo, SwapTokenInfo}, }; - use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; - -use super::logging::log_slippage_error; +use vipers::{assert_keys_eq, assert_keys_neq, invariant}; /// Checks if the reserve of the swap is the given key. fn check_reserves_match(token: &SwapTokenInfo, reserves_info_key: &Pubkey) -> ProgramResult { - check_token_keys_equal!( - token, - *reserves_info_key, - token.reserves, - "Reserves", - SwapError::IncorrectSwapAccount - ); + if token.index == 0 { + assert_keys_eq!( + reserves_info_key, + token.reserves, + SwapError::IncorrectSwapAccount, + "Reserves A" + ); + } else if token.index == 1 { + assert_keys_eq!( + reserves_info_key, + token.reserves, + SwapError::IncorrectSwapAccount, + "Reserves B" + ); + } else { + assert_keys_eq!( + reserves_info_key, + token.reserves, + SwapError::IncorrectSwapAccount, + "Reserves", + ); + } Ok(()) } @@ -30,15 +46,16 @@ pub fn check_has_admin_signer( expected_admin_key: &Pubkey, admin_account_info: &AccountInfo, ) -> ProgramResult { - check_keys_equal!( - *expected_admin_key, - *admin_account_info.key, + assert_keys_eq!( + expected_admin_key, + admin_account_info.key, + SwapError::Unauthorized, "Admin signer", - SwapError::Unauthorized ); - if !admin_account_info.is_signer { - return Err(ProgramError::MissingRequiredSignature); - } + invariant!( + admin_account_info.is_signer, + ProgramError::MissingRequiredSignature + ); Ok(()) } @@ -47,13 +64,32 @@ pub fn check_deposit_token_accounts( source_key: &Pubkey, reserves_info_key: &Pubkey, ) -> ProgramResult { - check_token_keys_not_equal!( - token, - *source_key, - token.reserves, - "Source account cannot be swap token account of token", - SwapError::InvalidInput - ); + match token.index { + 0 => { + assert_keys_neq!( + source_key, + token.reserves, + SwapError::InvalidInput, + "Source account cannot be one of swap's token accounts for token A", + ); + } + 1 => { + assert_keys_neq!( + source_key, + token.reserves, + SwapError::InvalidInput, + "Source account cannot be one of swap's token accounts for token B", + ); + } + _ => { + assert_keys_neq!( + source_key, + token.reserves, + SwapError::InvalidInput, + "Source account cannot be one of swap's token accounts", + ); + } + }; check_reserves_match(token, reserves_info_key)?; Ok(()) } @@ -78,11 +114,11 @@ pub fn check_withdraw_token_accounts( admin_fee_dest_key: &Pubkey, ) -> ProgramResult { check_reserves_match(token, reserves_info_key)?; - check_keys_equal!( - *admin_fee_dest_key, + assert_keys_eq!( + admin_fee_dest_key, token.admin_fees, + SwapError::InvalidAdmin, "Admin fee dest", - SwapError::InvalidAdmin ); Ok(()) } @@ -94,11 +130,11 @@ pub fn check_swap_authority( swap_authority_key: &Pubkey, ) -> ProgramResult { let swap_authority = utils::authority_id(program_id, swap_info_key, token_swap.nonce)?; - check_keys_equal!( - *swap_authority_key, + assert_keys_eq!( + swap_authority_key, swap_authority, + SwapError::InvalidProgramAddress, "Swap authority", - SwapError::InvalidProgramAddress ); Ok(()) } @@ -109,17 +145,17 @@ pub fn check_swap_token_destination_accounts( swap_destination_info_key: &Pubkey, admin_destination_info_key: &Pubkey, ) -> ProgramResult { - check_keys_equal!( - *swap_destination_info_key, + assert_keys_eq!( + swap_destination_info_key, token.reserves, + SwapError::IncorrectSwapAccount, "Incorrect destination, expected", - SwapError::IncorrectSwapAccount ); - check_keys_equal!( - *admin_destination_info_key, + assert_keys_eq!( + admin_destination_info_key, token.admin_fees, + SwapError::InvalidAdmin, "Admin fee", - SwapError::InvalidAdmin ); Ok(()) } diff --git a/stable-swap-program/program/src/processor/logging.rs b/stable-swap-program/program/src/processor/logging.rs index a7bdac8f..856402fc 100644 --- a/stable-swap-program/program/src/processor/logging.rs +++ b/stable-swap-program/program/src/processor/logging.rs @@ -2,7 +2,6 @@ use solana_program::log::sol_log_64; use solana_program::msg; -use solana_program::pubkey::Pubkey; /// Event enum #[derive(Debug)] @@ -47,30 +46,6 @@ pub fn log_event( ); } -pub fn log_keys_mismatch(msg: &str, left: Pubkey, right: Pubkey) { - msg!(msg); - msg!("Left:"); - left.log(); - msg!("Right:"); - right.log(); -} - -pub fn log_keys_mismatch_optional(msg: &str, left: Option, right: Option) { - msg!(msg); - msg!("Left:"); - if let Some(left_inner) = left { - left_inner.log(); - } else { - msg!("left: missing"); - } - msg!("Right:"); - if let Some(right_inner) = right { - right_inner.log(); - } else { - msg!("right: missing"); - } -} - /// Log slippage error pub fn log_slippage_error(minimum_amount: u64, computed_amount: u64) { sol_log_64(0, 0, 0, minimum_amount, computed_amount); diff --git a/stable-swap-program/program/src/processor/macros.rs b/stable-swap-program/program/src/processor/macros.rs deleted file mode 100644 index 386b23f6..00000000 --- a/stable-swap-program/program/src/processor/macros.rs +++ /dev/null @@ -1,75 +0,0 @@ -/// Checks if two pubkeys are equal, and if not, throws an error. -macro_rules! check_keys_equal { - ($left:expr, $right:expr, $msg:expr, $err:expr) => { - check_keys_condition!($left, $right, $msg, $err, $left == $right); - }; -} - -/// Checks if two pubkeys are not equal, and if not, throws an error. -macro_rules! check_keys_not_equal { - ($left:expr, $right:expr, $msg:expr, $err:expr) => { - check_keys_condition!($left, $right, $msg, $err, $left != $right); - }; -} - -macro_rules! check_keys_condition { - ($left:expr, $right:expr, $msg:expr, $err:expr, $continueExpr:expr) => { - if !$continueExpr { - $crate::processor::logging::log_keys_mismatch( - concat!($msg, " mismatch:"), - $left, - $right, - ); - return Err::<(), ProgramError>($err.into()); - } - }; -} - -/// Checks if two pubkeys are equal and exist, and if not, throws an error. -macro_rules! check_keys_equal_optional { - ($left:expr, $right:expr, $msg:expr, $err:expr) => { - check_keys_condition_optional!($left, $right, $msg, $err, $left == $right); - }; -} - -macro_rules! check_keys_condition_optional { - ($left:expr, $right:expr, $msg:expr, $err:expr, $continueExpr:expr) => { - match ($left.into(), $right.into()) { - (Some(_), Some(_)) if $continueExpr => (), - (left, right) => { - $crate::processor::logging::log_keys_mismatch_optional( - concat!($msg, " mismatch:"), - left, - right, - ); - return Err::<(), ProgramError>($err.into()); - } - } - }; -} - -/// Checks if two pubkeys are equal, and if not, throws an error. -macro_rules! check_token_keys_equal { - ($token: expr,$left:expr, $right:expr, $msg:expr, $err:expr) => { - check_token_keys_condition!($token, $left, $right, $msg, $err, $left == $right); - }; -} - -/// Checks if two pubkeys are not equal, and if not, throws an error. -macro_rules! check_token_keys_not_equal { - ($token: expr,$left:expr, $right:expr, $msg:expr, $err:expr) => { - check_token_keys_condition!($token, $left, $right, $msg, $err, $left != $right); - }; -} - -macro_rules! check_token_keys_condition { - ($token:expr, $left:expr, $right:expr, $msg:expr, $err:expr, $continueExpr:expr) => { - if ($token.index == 0) { - check_keys_condition!($left, $right, concat!($msg, " A"), $err, $continueExpr); - } else if ($token.index == 1) { - check_keys_condition!($left, $right, concat!($msg, " B"), $err, $continueExpr); - } else { - check_keys_condition!($left, $right, concat!($msg), $err, $continueExpr); - } - }; -} diff --git a/stable-swap-program/program/src/processor/mod.rs b/stable-swap-program/program/src/processor/mod.rs index daa2da2d..1cf91515 100644 --- a/stable-swap-program/program/src/processor/mod.rs +++ b/stable-swap-program/program/src/processor/mod.rs @@ -1,8 +1,5 @@ //! Program state processor -#[macro_use] -mod macros; - mod admin; mod checks; mod logging; @@ -10,8 +7,6 @@ mod swap; mod token; mod utils; -#[cfg(test)] -#[allow(clippy::unwrap_used)] mod test_utils; use crate::instruction::AdminInstruction; diff --git a/stable-swap-program/program/src/processor/swap.rs b/stable-swap-program/program/src/processor/swap.rs index 02a0b90f..b889496e 100644 --- a/stable-swap-program/program/src/processor/swap.rs +++ b/stable-swap-program/program/src/processor/swap.rs @@ -1,5 +1,7 @@ //! Module for processing non-admin pool instructions. +use anchor_lang::prelude::*; + use crate::{ error::SwapError, fees::Fees, @@ -17,12 +19,11 @@ use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, msg, - program_error::ProgramError, - program_option::COption, program_pack::Pack, pubkey::Pubkey, sysvar::{clock::Clock, Sysvar}, }; +use vipers::{assert_keys_eq, assert_keys_neq, invariant}; use super::checks::*; use super::logging::*; @@ -121,110 +122,107 @@ fn process_initialize( } let token_swap = SwapInfo::unpack_unchecked(&swap_info.data.borrow())?; - if token_swap.is_initialized { - return Err(SwapError::AlreadyInUse.into()); - } + invariant!(!token_swap.is_initialized, AlreadyInUse); let swap_authority = utils::authority_id(program_id, swap_info.key, nonce)?; - check_keys_equal!( - *authority_info.key, + assert_keys_eq!( + authority_info, swap_authority, + SwapError::InvalidProgramAddress, "Swap authority", - SwapError::InvalidProgramAddress ); let destination = utils::unpack_token_account(&destination_info.data.borrow())?; let token_a = utils::unpack_token_account(&token_a_info.data.borrow())?; let token_b = utils::unpack_token_account(&token_b_info.data.borrow())?; - check_keys_equal!( - *authority_info.key, + assert_keys_eq!( + authority_info, token_a.owner, + SwapError::InvalidOwner, "Token A authority", - SwapError::InvalidOwner ); - check_keys_equal!( - *authority_info.key, + assert_keys_eq!( + authority_info, token_b.owner, + SwapError::InvalidOwner, "Token B authority", - SwapError::InvalidOwner ); - check_keys_not_equal!( - *authority_info.key, + assert_keys_neq!( + authority_info, destination.owner, + SwapError::InvalidOutputOwner, "Initial LP destination authority", - SwapError::InvalidOutputOwner ); - if token_a.mint == token_b.mint { - return Err(SwapError::RepeatedMint.into()); - } - if token_b.amount == 0 { - return Err(SwapError::EmptySupply.into()); - } - if token_a.amount == 0 { - return Err(SwapError::EmptySupply.into()); - } - if token_a.delegate.is_some() { - return Err(SwapError::InvalidDelegate.into()); - } - if token_b.delegate.is_some() { - return Err(SwapError::InvalidDelegate.into()); - } - check_keys_equal!( + invariant!(token_a.mint != token_b.mint, RepeatedMint); + + invariant!(token_a.amount != 0, EmptySupply); + invariant!(token_a.delegate.is_none(), InvalidDelegate); + assert_keys_eq!( token_a.mint, - *token_a_mint_info.key, + token_a_mint_info, + SwapError::IncorrectMint, "Mint A", - SwapError::IncorrectMint ); - check_keys_equal!( + invariant!(token_a.close_authority.is_none(), InvalidCloseAuthority); + + invariant!(token_b.amount != 0, EmptySupply); + invariant!(token_b.delegate.is_none(), InvalidDelegate); + assert_keys_eq!( token_b.mint, - *token_b_mint_info.key, + token_b_mint_info, + SwapError::IncorrectMint, "Mint B", - SwapError::IncorrectMint ); - if token_a.close_authority.is_some() { - return Err(SwapError::InvalidCloseAuthority.into()); - } - if token_b.close_authority.is_some() { - return Err(SwapError::InvalidCloseAuthority.into()); - } + invariant!(token_b.close_authority.is_none(), InvalidCloseAuthority); + let pool_mint = utils::unpack_mint(&pool_mint_info.data.borrow())?; - check_keys_equal_optional!( - pool_mint.mint_authority, - COption::Some(*authority_info.key), + assert_keys_eq!( + pool_mint.mint_authority.unwrap(), + authority_info, + SwapError::InvalidOwner, "LP mint authority", - SwapError::InvalidOwner ); - if pool_mint.freeze_authority.is_some() { - return Err(SwapError::InvalidFreezeAuthority.into()); - } - if pool_mint.supply != 0 { - return Err(SwapError::InvalidSupply.into()); - } + invariant!(pool_mint.freeze_authority.is_none(), InvalidFreezeAuthority); + invariant!(pool_mint.supply == 0, InvalidSupply); + let token_a_mint = utils::unpack_mint(&token_a_mint_info.data.borrow())?; let token_b_mint = utils::unpack_mint(&token_b_mint_info.data.borrow())?; - if token_a_mint.decimals != token_b_mint.decimals { - return Err(SwapError::MismatchedDecimals.into()); - } - if pool_mint.decimals != token_a_mint.decimals { - return Err(SwapError::MismatchedDecimals.into()); - } + invariant!( + token_a_mint.decimals == token_b_mint.decimals, + MismatchedDecimals + ); + invariant!( + pool_mint.decimals == token_b_mint.decimals, + MismatchedDecimals + ); + let admin_fee_key_a = utils::unpack_token_account(&admin_fee_a_info.data.borrow())?; let admin_fee_key_b = utils::unpack_token_account(&admin_fee_b_info.data.borrow())?; - check_keys_equal!( + assert_keys_eq!( token_a.mint, admin_fee_key_a.mint, + SwapError::InvalidAdmin, "Mint A", - SwapError::InvalidAdmin ); - check_keys_equal!( + assert_keys_eq!( token_b.mint, admin_fee_key_b.mint, + SwapError::InvalidAdmin, "Mint B", - SwapError::InvalidAdmin ); + assert_keys_eq!(admin_fee_a_info.owner, anchor_spl::token::ID); + assert_keys_eq!(admin_fee_b_info.owner, anchor_spl::token::ID); + assert_keys_eq!(token_a_mint_info.owner, anchor_spl::token::ID); + assert_keys_eq!(token_a_info.owner, anchor_spl::token::ID); + assert_keys_eq!(token_b_mint_info.owner, anchor_spl::token::ID); + assert_keys_eq!(token_b_info.owner, anchor_spl::token::ID); + assert_keys_eq!(pool_mint_info.owner, anchor_spl::token::ID); + assert_keys_eq!(destination_info.owner, anchor_spl::token::ID); + assert_keys_eq!(token_program_info, anchor_spl::token::ID); + // amp_factor == initial_amp_factor == target_amp_factor on init let invariant = StableSwap::new(amp_factor, amp_factor, ZERO_TS, ZERO_TS, ZERO_TS); // Compute amount of LP tokens to mint for bootstrapper @@ -302,29 +300,26 @@ fn process_swap( let admin_destination_info = next_account_info(account_info_iter)?; let token_program_info = next_account_info(account_info_iter)?; - if *swap_source_info.key == *swap_destination_info.key { - return Err(SwapError::InvalidInput.into()); - } + assert_keys_neq!( + swap_source_info, + swap_destination_info, + SwapError::InvalidInput + ); let token_swap = SwapInfo::unpack(&swap_info.data.borrow())?; - if token_swap.is_paused { - return Err(SwapError::IsPaused.into()); - } + invariant!(!token_swap.is_paused, SwapError::IsPaused); - check_token_keys_not_equal!( - token_swap.token_a, - *source_info.key, + assert_keys_neq!( + source_info.key, token_swap.token_a.reserves, - "Source account cannot be one of swap's token accounts for token", - SwapError::InvalidInput + SwapError::InvalidInput, + "Source account cannot be one of swap's token accounts for token A", ); - - check_token_keys_not_equal!( - token_swap.token_b, - *source_info.key, + assert_keys_neq!( + source_info.key, token_swap.token_b.reserves, - "Source account cannot be one of swap's token accounts for token", - SwapError::InvalidInput + SwapError::InvalidInput, + "Source account cannot be one of swap's token accounts for token B", ); check_swap_authority( @@ -440,9 +435,7 @@ fn process_deposit( let token_program_info = next_account_info(account_info_iter)?; let token_swap = SwapInfo::unpack(&swap_info.data.borrow())?; - if token_swap.is_paused { - return Err(SwapError::IsPaused.into()); - } + invariant!(!token_swap.is_paused, SwapError::IsPaused); check_swap_authority( &token_swap, swap_info.key, @@ -453,11 +446,11 @@ fn process_deposit( check_deposit_token_accounts(&token_swap.token_a, source_a_info.key, token_a_info.key)?; check_deposit_token_accounts(&token_swap.token_b, source_b_info.key, token_b_info.key)?; - check_keys_equal!( - *pool_mint_info.key, + assert_keys_eq!( + pool_mint_info, token_swap.pool_mint, + SwapError::IncorrectMint, "Mint A", - SwapError::IncorrectMint ); let clock = Clock::get()?; @@ -608,11 +601,11 @@ fn process_withdraw( admin_fee_dest_b_info.key, )?; - check_keys_equal!( - *pool_mint_info.key, + assert_keys_eq!( + pool_mint_info, token_swap.pool_mint, + SwapError::IncorrectMint, "Pool mint", - SwapError::IncorrectMint ); let pool_mint = utils::unpack_mint(&pool_mint_info.data.borrow())?; @@ -701,14 +694,10 @@ fn process_withdraw_one( let admin_destination_info = next_account_info(account_info_iter)?; let token_program_info = next_account_info(account_info_iter)?; - if *base_token_info.key == *quote_token_info.key { - return Err(SwapError::InvalidInput.into()); - } + assert_keys_neq!(base_token_info, quote_token_info, SwapError::InvalidInput); let token_swap = SwapInfo::unpack(&swap_info.data.borrow())?; - if token_swap.is_paused { - return Err(SwapError::IsPaused.into()); - } + invariant!(!token_swap.is_paused, SwapError::IsPaused); check_swap_authority( &token_swap, swap_info.key, @@ -717,30 +706,30 @@ fn process_withdraw_one( )?; if *base_token_info.key == token_swap.token_a.reserves { - check_keys_equal!( - *quote_token_info.key, + assert_keys_eq!( + quote_token_info, token_swap.token_b.reserves, + SwapError::IncorrectSwapAccount, "Swap A -> B reserves", - SwapError::IncorrectSwapAccount ); - check_keys_equal!( - *admin_destination_info.key, + assert_keys_eq!( + admin_destination_info, token_swap.token_a.admin_fees, + SwapError::InvalidAdmin, "Swap A -> B admin fee destination", - SwapError::InvalidAdmin ); } else if *base_token_info.key == token_swap.token_b.reserves { - check_keys_equal!( - *quote_token_info.key, + assert_keys_eq!( + quote_token_info, token_swap.token_a.reserves, + SwapError::IncorrectSwapAccount, "Swap B -> A reserves", - SwapError::IncorrectSwapAccount ); - check_keys_equal!( - *admin_destination_info.key, + assert_keys_eq!( + admin_destination_info, token_swap.token_b.admin_fees, + SwapError::InvalidAdmin, "Swap B -> A admin fee destination", - SwapError::InvalidAdmin ); } else { msg!("Unknown base token:"); @@ -748,11 +737,11 @@ fn process_withdraw_one( return Err(SwapError::IncorrectSwapAccount.into()); } - check_keys_equal!( - *pool_mint_info.key, + assert_keys_eq!( + pool_mint_info, token_swap.pool_mint, + SwapError::IncorrectMint, "Pool mint", - SwapError::IncorrectMint ); let pool_mint = utils::unpack_mint(&pool_mint_info.data.borrow())?; diff --git a/stable-swap-program/program/src/processor/test_utils.rs b/stable-swap-program/program/src/processor/test_utils.rs index d6608068..e1794060 100644 --- a/stable-swap-program/program/src/processor/test_utils.rs +++ b/stable-swap-program/program/src/processor/test_utils.rs @@ -1,4 +1,6 @@ //! Test utility methods +#![cfg(test)] +#![allow(clippy::unwrap_used)] #![allow(clippy::too_many_arguments)] use crate::{curve::ZERO_TS, fees::Fees, instruction::*, processor::Processor, state::SwapInfo};