use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use crate::{
states::{
FeedConfig, PriceProviderKind, Store, TokenConfigExt, TokenMapAccess, TokenMapAccessMut,
TokenMapHeader, TokenMapLoader, UpdateTokenConfigParams,
},
utils::internal,
CoreError,
};
#[derive(Accounts)]
pub struct InitializeTokenMap<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(
init,
payer = payer,
space = 8 + TokenMapHeader::space(0),
)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
pub system_program: Program<'info, System>,
}
pub(crate) fn initialize_token_map(ctx: Context<InitializeTokenMap>) -> Result<()> {
ctx.accounts.token_map.load_init()?.store = ctx.accounts.store.key();
Ok(())
}
#[derive(Accounts)]
pub struct PushToTokenMap<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(
mut,
has_one = store,
)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
pub token: Account<'info, Mint>,
pub system_program: Program<'info, System>,
}
pub(crate) fn unchecked_push_to_token_map(
ctx: Context<PushToTokenMap>,
name: &str,
builder: UpdateTokenConfigParams,
enable: bool,
new: bool,
) -> Result<()> {
let token = ctx.accounts.token.key();
let token_decimals = ctx.accounts.token.decimals;
do_push_token_map(
ctx.accounts.authority.to_account_info(),
&ctx.accounts.token_map,
ctx.accounts.system_program.to_account_info(),
false,
name,
&token,
token_decimals,
builder,
enable,
new,
)
}
impl<'info> internal::Authentication<'info> for PushToTokenMap<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct PushToTokenMapSynthetic<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
pub system_program: Program<'info, System>,
}
pub(crate) fn unchecked_push_to_token_map_synthetic(
ctx: Context<PushToTokenMapSynthetic>,
name: &str,
token: Pubkey,
token_decimals: u8,
builder: UpdateTokenConfigParams,
enable: bool,
new: bool,
) -> Result<()> {
do_push_token_map(
ctx.accounts.authority.to_account_info(),
&ctx.accounts.token_map,
ctx.accounts.system_program.to_account_info(),
true,
name,
&token,
token_decimals,
builder,
enable,
new,
)
}
impl<'info> internal::Authentication<'info> for PushToTokenMapSynthetic<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct ToggleTokenConfig<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(
mut,
has_one = store,
)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
}
pub(crate) fn unchecked_toggle_token_config(
ctx: Context<ToggleTokenConfig>,
token: Pubkey,
enable: bool,
) -> Result<()> {
ctx.accounts
.token_map
.load_token_map_mut()?
.get_mut(&token)
.ok_or_else(|| error!(CoreError::NotFound))?
.set_enabled(enable);
Ok(())
}
impl<'info> internal::Authentication<'info> for ToggleTokenConfig<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct SetExpectedProvider<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
}
pub(crate) fn unchecked_set_expected_provider(
ctx: Context<SetExpectedProvider>,
token: Pubkey,
provider: PriceProviderKind,
) -> Result<()> {
let mut token_map = ctx.accounts.token_map.load_token_map_mut()?;
let config = token_map
.get_mut(&token)
.ok_or_else(|| error!(CoreError::NotFound))?;
require_neq!(
config.expected_provider().map_err(CoreError::from)?,
provider,
CoreError::PreconditionsAreNotMet
);
config.set_expected_provider(provider);
Ok(())
}
impl<'info> internal::Authentication<'info> for SetExpectedProvider<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct SetFeedConfig<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
}
pub(crate) fn unchecked_set_feed_config(
ctx: Context<SetFeedConfig>,
token: Pubkey,
provider: &PriceProviderKind,
feed: Pubkey,
timestamp_adjustment: u32,
) -> Result<()> {
ctx.accounts
.token_map
.load_token_map_mut()?
.get_mut(&token)
.ok_or_else(|| error!(CoreError::NotFound))?
.set_feed_config(
provider,
FeedConfig::new(feed).with_timestamp_adjustment(timestamp_adjustment),
)
.map_err(CoreError::from)
.map_err(|err| error!(err))
}
impl<'info> internal::Authentication<'info> for SetFeedConfig<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct ReadTokenMap<'info> {
pub token_map: AccountLoader<'info, TokenMapHeader>,
}
pub(crate) fn is_token_config_enabled(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<bool> {
ctx.accounts
.token_map
.load_token_map()?
.get(token)
.map(|config| config.is_enabled())
.ok_or_else(|| error!(CoreError::NotFound))
}
pub(crate) fn token_expected_provider(
ctx: Context<ReadTokenMap>,
token: &Pubkey,
) -> Result<PriceProviderKind> {
ctx.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.expected_provider()
.map_err(CoreError::from)
.map_err(|err| error!(err))
}
pub(crate) fn token_feed(
ctx: Context<ReadTokenMap>,
token: &Pubkey,
provider: &PriceProviderKind,
) -> Result<Pubkey> {
ctx.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.get_feed(provider)
.map_err(CoreError::from)
.map_err(|err| error!(err))
}
pub(crate) fn token_timestamp_adjustment(
ctx: Context<ReadTokenMap>,
token: &Pubkey,
provider: &PriceProviderKind,
) -> Result<u32> {
ctx.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.timestamp_adjustment(provider)
.map_err(CoreError::from)
.map_err(|err| error!(err))
}
pub(crate) fn token_name(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<String> {
ctx.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.name()
.map(|s| s.to_owned())
.map_err(CoreError::from)
.map_err(|err| error!(err))
}
pub(crate) fn token_decimals(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
Ok(ctx
.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.token_decimals())
}
pub(crate) fn token_precision(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
Ok(ctx
.accounts
.token_map
.load_token_map()?
.get(token)
.ok_or_else(|| error!(CoreError::NotFound))?
.precision())
}
#[allow(clippy::too_many_arguments)]
fn do_push_token_map<'info>(
authority: AccountInfo<'info>,
token_map_loader: &AccountLoader<'info, TokenMapHeader>,
system_program: AccountInfo<'info>,
synthetic: bool,
name: &str,
token: &Pubkey,
token_decimals: u8,
builder: UpdateTokenConfigParams,
enable: bool,
new: bool,
) -> Result<()> {
{
let new_space = 8 + token_map_loader.load()?.space_after_push()?;
let current_space = token_map_loader.as_ref().data_len();
let current_lamports = token_map_loader.as_ref().lamports();
let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
if new_space > current_space {
if current_lamports < new_rent_minimum {
anchor_lang::system_program::transfer(
CpiContext::new(
system_program,
anchor_lang::system_program::Transfer {
from: authority,
to: token_map_loader.to_account_info(),
},
),
new_rent_minimum.saturating_sub(current_lamports),
)?;
}
token_map_loader.as_ref().realloc(new_space, false)?;
}
}
let mut token_map = token_map_loader.load_token_map_mut()?;
token_map.push_with(
token,
|config| config.update(name, synthetic, token_decimals, builder, enable, new),
new,
)?;
Ok(())
}