use {
crate::types::WhirlpoolRewardInfo,
carbon_core::{
account::AccountMetadata,
postgres::{
metadata::AccountRowMetadata,
primitives::{Pubkey, U128, U16, U64},
},
},
};
#[derive(sqlx::FromRow, Debug, Clone)]
pub struct WhirlpoolRow {
#[sqlx(flatten)]
pub account_metadata: AccountRowMetadata,
pub whirlpools_config: Pubkey,
pub whirlpool_bump: Vec<u8>,
pub tick_spacing: U16,
pub fee_tier_index_seed: Vec<u8>,
pub fee_rate: U16,
pub protocol_fee_rate: U16,
pub liquidity: U128,
pub sqrt_price: U128,
pub tick_current_index: i32,
pub protocol_fee_owed_a: U64,
pub protocol_fee_owed_b: U64,
pub token_mint_a: Pubkey,
pub token_vault_a: Pubkey,
pub fee_growth_global_a: U128,
pub token_mint_b: Pubkey,
pub token_vault_b: Pubkey,
pub fee_growth_global_b: U128,
pub reward_last_updated_timestamp: U64,
pub reward_infos: sqlx::types::Json<Vec<WhirlpoolRewardInfo>>,
}
impl WhirlpoolRow {
pub fn from_parts(
source: crate::accounts::whirlpool::Whirlpool,
metadata: AccountMetadata,
) -> Self {
Self {
account_metadata: metadata.into(),
whirlpools_config: source.whirlpools_config.into(),
whirlpool_bump: source.whirlpool_bump.to_vec(),
tick_spacing: source.tick_spacing.into(),
fee_tier_index_seed: source.fee_tier_index_seed.to_vec(),
fee_rate: source.fee_rate.into(),
protocol_fee_rate: source.protocol_fee_rate.into(),
liquidity: source.liquidity.into(),
sqrt_price: source.sqrt_price.into(),
tick_current_index: source.tick_current_index,
protocol_fee_owed_a: source.protocol_fee_owed_a.into(),
protocol_fee_owed_b: source.protocol_fee_owed_b.into(),
token_mint_a: source.token_mint_a.into(),
token_vault_a: source.token_vault_a.into(),
fee_growth_global_a: source.fee_growth_global_a.into(),
token_mint_b: source.token_mint_b.into(),
token_vault_b: source.token_vault_b.into(),
fee_growth_global_b: source.fee_growth_global_b.into(),
reward_last_updated_timestamp: source.reward_last_updated_timestamp.into(),
reward_infos: sqlx::types::Json(source.reward_infos.to_vec()),
}
}
}
impl TryFrom<WhirlpoolRow> for crate::accounts::whirlpool::Whirlpool {
type Error = carbon_core::error::Error;
fn try_from(source: WhirlpoolRow) -> Result<Self, Self::Error> {
Ok(Self {
whirlpools_config: *source.whirlpools_config,
whirlpool_bump: source.whirlpool_bump.as_slice().try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 1 bytes"
.to_string(),
)
})?,
tick_spacing: source.tick_spacing.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fee_tier_index_seed: source.fee_tier_index_seed.as_slice().try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 2 bytes"
.to_string(),
)
},
)?,
fee_rate: source.fee_rate.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
protocol_fee_rate: source.protocol_fee_rate.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
liquidity: *source.liquidity,
sqrt_price: *source.sqrt_price,
tick_current_index: source.tick_current_index,
protocol_fee_owed_a: *source.protocol_fee_owed_a,
protocol_fee_owed_b: *source.protocol_fee_owed_b,
token_mint_a: *source.token_mint_a,
token_vault_a: *source.token_vault_a,
fee_growth_global_a: *source.fee_growth_global_a,
token_mint_b: *source.token_mint_b,
token_vault_b: *source.token_vault_b,
fee_growth_global_b: *source.fee_growth_global_b,
reward_last_updated_timestamp: *source.reward_last_updated_timestamp,
reward_infos: source
.reward_infos
.0
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
})
}
}
impl carbon_core::postgres::operations::Table for crate::accounts::whirlpool::Whirlpool {
fn table() -> &'static str {
"whirlpool_account"
}
fn columns() -> Vec<&'static str> {
vec![
"__pubkey",
"__slot",
"whirlpools_config",
"whirlpool_bump",
"tick_spacing",
"fee_tier_index_seed",
"fee_rate",
"protocol_fee_rate",
"liquidity",
"sqrt_price",
"tick_current_index",
"protocol_fee_owed_a",
"protocol_fee_owed_b",
"token_mint_a",
"token_vault_a",
"fee_growth_global_a",
"token_mint_b",
"token_vault_b",
"fee_growth_global_b",
"reward_last_updated_timestamp",
"reward_infos",
]
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Insert for WhirlpoolRow {
async fn insert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"
INSERT INTO whirlpool_account (
"whirlpools_config",
"whirlpool_bump",
"tick_spacing",
"fee_tier_index_seed",
"fee_rate",
"protocol_fee_rate",
"liquidity",
"sqrt_price",
"tick_current_index",
"protocol_fee_owed_a",
"protocol_fee_owed_b",
"token_mint_a",
"token_vault_a",
"fee_growth_global_a",
"token_mint_b",
"token_vault_b",
"fee_growth_global_b",
"reward_last_updated_timestamp",
"reward_infos",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21
)"#)
.bind(self.whirlpools_config)
.bind(&self.whirlpool_bump)
.bind(self.tick_spacing)
.bind(&self.fee_tier_index_seed)
.bind(self.fee_rate)
.bind(self.protocol_fee_rate)
.bind(&self.liquidity)
.bind(&self.sqrt_price)
.bind(self.tick_current_index)
.bind(&self.protocol_fee_owed_a)
.bind(&self.protocol_fee_owed_b)
.bind(self.token_mint_a)
.bind(self.token_vault_a)
.bind(&self.fee_growth_global_a)
.bind(self.token_mint_b)
.bind(self.token_vault_b)
.bind(&self.fee_growth_global_b)
.bind(&self.reward_last_updated_timestamp)
.bind(&self.reward_infos)
.bind(self.account_metadata.pubkey)
.bind(&self.account_metadata.slot)
.execute(pool).await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Upsert for WhirlpoolRow {
async fn upsert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"INSERT INTO whirlpool_account (
"whirlpools_config",
"whirlpool_bump",
"tick_spacing",
"fee_tier_index_seed",
"fee_rate",
"protocol_fee_rate",
"liquidity",
"sqrt_price",
"tick_current_index",
"protocol_fee_owed_a",
"protocol_fee_owed_b",
"token_mint_a",
"token_vault_a",
"fee_growth_global_a",
"token_mint_b",
"token_vault_b",
"fee_growth_global_b",
"reward_last_updated_timestamp",
"reward_infos",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21
) ON CONFLICT (
__pubkey
) DO UPDATE SET
"whirlpools_config" = EXCLUDED."whirlpools_config",
"whirlpool_bump" = EXCLUDED."whirlpool_bump",
"tick_spacing" = EXCLUDED."tick_spacing",
"fee_tier_index_seed" = EXCLUDED."fee_tier_index_seed",
"fee_rate" = EXCLUDED."fee_rate",
"protocol_fee_rate" = EXCLUDED."protocol_fee_rate",
"liquidity" = EXCLUDED."liquidity",
"sqrt_price" = EXCLUDED."sqrt_price",
"tick_current_index" = EXCLUDED."tick_current_index",
"protocol_fee_owed_a" = EXCLUDED."protocol_fee_owed_a",
"protocol_fee_owed_b" = EXCLUDED."protocol_fee_owed_b",
"token_mint_a" = EXCLUDED."token_mint_a",
"token_vault_a" = EXCLUDED."token_vault_a",
"fee_growth_global_a" = EXCLUDED."fee_growth_global_a",
"token_mint_b" = EXCLUDED."token_mint_b",
"token_vault_b" = EXCLUDED."token_vault_b",
"fee_growth_global_b" = EXCLUDED."fee_growth_global_b",
"reward_last_updated_timestamp" = EXCLUDED."reward_last_updated_timestamp",
"reward_infos" = EXCLUDED."reward_infos",
__slot = EXCLUDED.__slot
"#)
.bind(self.whirlpools_config)
.bind(&self.whirlpool_bump)
.bind(self.tick_spacing)
.bind(&self.fee_tier_index_seed)
.bind(self.fee_rate)
.bind(self.protocol_fee_rate)
.bind(&self.liquidity)
.bind(&self.sqrt_price)
.bind(self.tick_current_index)
.bind(&self.protocol_fee_owed_a)
.bind(&self.protocol_fee_owed_b)
.bind(self.token_mint_a)
.bind(self.token_vault_a)
.bind(&self.fee_growth_global_a)
.bind(self.token_mint_b)
.bind(self.token_vault_b)
.bind(&self.fee_growth_global_b)
.bind(&self.reward_last_updated_timestamp)
.bind(&self.reward_infos)
.bind(self.account_metadata.pubkey)
.bind(&self.account_metadata.slot)
.execute(pool).await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Delete for WhirlpoolRow {
type Key = carbon_core::postgres::primitives::Pubkey;
async fn delete(key: Self::Key, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(
r#"DELETE FROM whirlpool_account WHERE
__pubkey = $1
"#,
)
.bind(key)
.execute(pool)
.await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Lookup for WhirlpoolRow {
type Key = carbon_core::postgres::primitives::Pubkey;
async fn lookup(
key: Self::Key,
pool: &sqlx::PgPool,
) -> carbon_core::error::CarbonResult<Option<Self>> {
let row = sqlx::query_as(
r#"SELECT * FROM whirlpool_account WHERE
__pubkey = $1
"#,
)
.bind(key)
.fetch_optional(pool)
.await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(row)
}
}
pub struct WhirlpoolMigrationOperation;
#[async_trait::async_trait]
impl sqlx_migrator::Operation<sqlx::Postgres> for WhirlpoolMigrationOperation {
async fn up(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(
r#"CREATE TABLE IF NOT EXISTS whirlpool_account (
-- Account data
"whirlpools_config" BYTEA NOT NULL,
"whirlpool_bump" BYTEA NOT NULL,
"tick_spacing" INT4 NOT NULL,
"fee_tier_index_seed" BYTEA NOT NULL,
"fee_rate" INT4 NOT NULL,
"protocol_fee_rate" INT4 NOT NULL,
"liquidity" NUMERIC(39) NOT NULL,
"sqrt_price" NUMERIC(39) NOT NULL,
"tick_current_index" INT4 NOT NULL,
"protocol_fee_owed_a" NUMERIC(20) NOT NULL,
"protocol_fee_owed_b" NUMERIC(20) NOT NULL,
"token_mint_a" BYTEA NOT NULL,
"token_vault_a" BYTEA NOT NULL,
"fee_growth_global_a" NUMERIC(39) NOT NULL,
"token_mint_b" BYTEA NOT NULL,
"token_vault_b" BYTEA NOT NULL,
"fee_growth_global_b" NUMERIC(39) NOT NULL,
"reward_last_updated_timestamp" NUMERIC(20) NOT NULL,
"reward_infos" JSONB NOT NULL,
-- Account metadata
__pubkey BYTEA NOT NULL,
__slot NUMERIC(20),
PRIMARY KEY (__pubkey)
)"#,
)
.execute(connection)
.await?;
Ok(())
}
async fn down(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(r#"DROP TABLE IF EXISTS whirlpool_account"#)
.execute(connection)
.await?;
Ok(())
}
}