use {
crate::types::UserFees,
carbon_core::{
account::AccountMetadata,
postgres::{
metadata::AccountRowMetadata,
primitives::{Pubkey, U16, U32, U64, U8},
},
},
};
#[derive(sqlx::FromRow, Debug, Clone)]
pub struct UserStatsRow {
#[sqlx(flatten)]
pub account_metadata: AccountRowMetadata,
pub authority: Pubkey,
pub referrer: Pubkey,
pub fees: sqlx::types::Json<UserFees>,
pub next_epoch_ts: i64,
pub maker_volume30d: U64,
pub taker_volume30d: U64,
pub filler_volume30d: U64,
pub last_maker_volume30d_ts: i64,
pub last_taker_volume30d_ts: i64,
pub last_filler_volume30d_ts: i64,
pub if_staked_quote_asset_amount: U64,
pub number_of_sub_accounts: U16,
pub number_of_sub_accounts_created: U16,
pub referrer_status: U8,
pub disable_update_perp_bid_ask_twap: U8,
pub paused_operations: U8,
pub fuel_overflow_status: U8,
pub fuel_insurance: U32,
pub fuel_deposits: U32,
pub fuel_borrows: U32,
pub fuel_positions: U32,
pub fuel_taker: U32,
pub fuel_maker: U32,
pub if_staked_gov_token_amount: U64,
pub last_fuel_if_bonus_update_ts: U32,
pub padding: Vec<u8>,
}
impl UserStatsRow {
pub fn from_parts(
source: crate::accounts::user_stats::UserStats,
metadata: AccountMetadata,
) -> Self {
Self {
account_metadata: metadata.into(),
authority: source.authority.into(),
referrer: source.referrer.into(),
fees: sqlx::types::Json(source.fees),
next_epoch_ts: source.next_epoch_ts,
maker_volume30d: source.maker_volume30d.into(),
taker_volume30d: source.taker_volume30d.into(),
filler_volume30d: source.filler_volume30d.into(),
last_maker_volume30d_ts: source.last_maker_volume30d_ts,
last_taker_volume30d_ts: source.last_taker_volume30d_ts,
last_filler_volume30d_ts: source.last_filler_volume30d_ts,
if_staked_quote_asset_amount: source.if_staked_quote_asset_amount.into(),
number_of_sub_accounts: source.number_of_sub_accounts.into(),
number_of_sub_accounts_created: source.number_of_sub_accounts_created.into(),
referrer_status: source.referrer_status.into(),
disable_update_perp_bid_ask_twap: source.disable_update_perp_bid_ask_twap.into(),
paused_operations: source.paused_operations.into(),
fuel_overflow_status: source.fuel_overflow_status.into(),
fuel_insurance: source.fuel_insurance.into(),
fuel_deposits: source.fuel_deposits.into(),
fuel_borrows: source.fuel_borrows.into(),
fuel_positions: source.fuel_positions.into(),
fuel_taker: source.fuel_taker.into(),
fuel_maker: source.fuel_maker.into(),
if_staked_gov_token_amount: source.if_staked_gov_token_amount.into(),
last_fuel_if_bonus_update_ts: source.last_fuel_if_bonus_update_ts.into(),
padding: source.padding.to_vec(),
}
}
}
impl TryFrom<UserStatsRow> for crate::accounts::user_stats::UserStats {
type Error = carbon_core::error::Error;
fn try_from(source: UserStatsRow) -> Result<Self, Self::Error> {
Ok(Self {
authority: *source.authority,
referrer: *source.referrer,
fees: source.fees.0,
next_epoch_ts: source.next_epoch_ts,
maker_volume30d: *source.maker_volume30d,
taker_volume30d: *source.taker_volume30d,
filler_volume30d: *source.filler_volume30d,
last_maker_volume30d_ts: source.last_maker_volume30d_ts,
last_taker_volume30d_ts: source.last_taker_volume30d_ts,
last_filler_volume30d_ts: source.last_filler_volume30d_ts,
if_staked_quote_asset_amount: *source.if_staked_quote_asset_amount,
number_of_sub_accounts: source.number_of_sub_accounts.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
number_of_sub_accounts_created: source
.number_of_sub_accounts_created
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
referrer_status: source.referrer_status.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
disable_update_perp_bid_ask_twap: source
.disable_update_perp_bid_ask_twap
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
paused_operations: source.paused_operations.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_overflow_status: source.fuel_overflow_status.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_insurance: source.fuel_insurance.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_deposits: source.fuel_deposits.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_borrows: source.fuel_borrows.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_positions: source.fuel_positions.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_taker: source.fuel_taker.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
fuel_maker: source.fuel_maker.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
if_staked_gov_token_amount: *source.if_staked_gov_token_amount,
last_fuel_if_bonus_update_ts: source.last_fuel_if_bonus_update_ts.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
padding: source.padding.as_slice().try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 12 bytes"
.to_string(),
)
})?,
})
}
}
impl carbon_core::postgres::operations::Table for crate::accounts::user_stats::UserStats {
fn table() -> &'static str {
"user_stats_account"
}
fn columns() -> Vec<&'static str> {
vec![
"__pubkey",
"__slot",
"authority",
"referrer",
"fees",
"next_epoch_ts",
"maker_volume30d",
"taker_volume30d",
"filler_volume30d",
"last_maker_volume30d_ts",
"last_taker_volume30d_ts",
"last_filler_volume30d_ts",
"if_staked_quote_asset_amount",
"number_of_sub_accounts",
"number_of_sub_accounts_created",
"referrer_status",
"disable_update_perp_bid_ask_twap",
"paused_operations",
"fuel_overflow_status",
"fuel_insurance",
"fuel_deposits",
"fuel_borrows",
"fuel_positions",
"fuel_taker",
"fuel_maker",
"if_staked_gov_token_amount",
"last_fuel_if_bonus_update_ts",
"padding",
]
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Insert for UserStatsRow {
async fn insert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"
INSERT INTO user_stats_account (
"authority",
"referrer",
"fees",
"next_epoch_ts",
"maker_volume30d",
"taker_volume30d",
"filler_volume30d",
"last_maker_volume30d_ts",
"last_taker_volume30d_ts",
"last_filler_volume30d_ts",
"if_staked_quote_asset_amount",
"number_of_sub_accounts",
"number_of_sub_accounts_created",
"referrer_status",
"disable_update_perp_bid_ask_twap",
"paused_operations",
"fuel_overflow_status",
"fuel_insurance",
"fuel_deposits",
"fuel_borrows",
"fuel_positions",
"fuel_taker",
"fuel_maker",
"if_staked_gov_token_amount",
"last_fuel_if_bonus_update_ts",
"padding",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28
)"#)
.bind(self.authority)
.bind(self.referrer)
.bind(&self.fees)
.bind(self.next_epoch_ts)
.bind(&self.maker_volume30d)
.bind(&self.taker_volume30d)
.bind(&self.filler_volume30d)
.bind(self.last_maker_volume30d_ts)
.bind(self.last_taker_volume30d_ts)
.bind(self.last_filler_volume30d_ts)
.bind(&self.if_staked_quote_asset_amount)
.bind(self.number_of_sub_accounts)
.bind(self.number_of_sub_accounts_created)
.bind(self.referrer_status)
.bind(self.disable_update_perp_bid_ask_twap)
.bind(self.paused_operations)
.bind(self.fuel_overflow_status)
.bind(self.fuel_insurance)
.bind(self.fuel_deposits)
.bind(self.fuel_borrows)
.bind(self.fuel_positions)
.bind(self.fuel_taker)
.bind(self.fuel_maker)
.bind(&self.if_staked_gov_token_amount)
.bind(self.last_fuel_if_bonus_update_ts)
.bind(&self.padding)
.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 UserStatsRow {
async fn upsert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"INSERT INTO user_stats_account (
"authority",
"referrer",
"fees",
"next_epoch_ts",
"maker_volume30d",
"taker_volume30d",
"filler_volume30d",
"last_maker_volume30d_ts",
"last_taker_volume30d_ts",
"last_filler_volume30d_ts",
"if_staked_quote_asset_amount",
"number_of_sub_accounts",
"number_of_sub_accounts_created",
"referrer_status",
"disable_update_perp_bid_ask_twap",
"paused_operations",
"fuel_overflow_status",
"fuel_insurance",
"fuel_deposits",
"fuel_borrows",
"fuel_positions",
"fuel_taker",
"fuel_maker",
"if_staked_gov_token_amount",
"last_fuel_if_bonus_update_ts",
"padding",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28
) ON CONFLICT (
__pubkey
) DO UPDATE SET
"authority" = EXCLUDED."authority",
"referrer" = EXCLUDED."referrer",
"fees" = EXCLUDED."fees",
"next_epoch_ts" = EXCLUDED."next_epoch_ts",
"maker_volume30d" = EXCLUDED."maker_volume30d",
"taker_volume30d" = EXCLUDED."taker_volume30d",
"filler_volume30d" = EXCLUDED."filler_volume30d",
"last_maker_volume30d_ts" = EXCLUDED."last_maker_volume30d_ts",
"last_taker_volume30d_ts" = EXCLUDED."last_taker_volume30d_ts",
"last_filler_volume30d_ts" = EXCLUDED."last_filler_volume30d_ts",
"if_staked_quote_asset_amount" = EXCLUDED."if_staked_quote_asset_amount",
"number_of_sub_accounts" = EXCLUDED."number_of_sub_accounts",
"number_of_sub_accounts_created" = EXCLUDED."number_of_sub_accounts_created",
"referrer_status" = EXCLUDED."referrer_status",
"disable_update_perp_bid_ask_twap" = EXCLUDED."disable_update_perp_bid_ask_twap",
"paused_operations" = EXCLUDED."paused_operations",
"fuel_overflow_status" = EXCLUDED."fuel_overflow_status",
"fuel_insurance" = EXCLUDED."fuel_insurance",
"fuel_deposits" = EXCLUDED."fuel_deposits",
"fuel_borrows" = EXCLUDED."fuel_borrows",
"fuel_positions" = EXCLUDED."fuel_positions",
"fuel_taker" = EXCLUDED."fuel_taker",
"fuel_maker" = EXCLUDED."fuel_maker",
"if_staked_gov_token_amount" = EXCLUDED."if_staked_gov_token_amount",
"last_fuel_if_bonus_update_ts" = EXCLUDED."last_fuel_if_bonus_update_ts",
"padding" = EXCLUDED."padding",
__slot = EXCLUDED.__slot
"#)
.bind(self.authority)
.bind(self.referrer)
.bind(&self.fees)
.bind(self.next_epoch_ts)
.bind(&self.maker_volume30d)
.bind(&self.taker_volume30d)
.bind(&self.filler_volume30d)
.bind(self.last_maker_volume30d_ts)
.bind(self.last_taker_volume30d_ts)
.bind(self.last_filler_volume30d_ts)
.bind(&self.if_staked_quote_asset_amount)
.bind(self.number_of_sub_accounts)
.bind(self.number_of_sub_accounts_created)
.bind(self.referrer_status)
.bind(self.disable_update_perp_bid_ask_twap)
.bind(self.paused_operations)
.bind(self.fuel_overflow_status)
.bind(self.fuel_insurance)
.bind(self.fuel_deposits)
.bind(self.fuel_borrows)
.bind(self.fuel_positions)
.bind(self.fuel_taker)
.bind(self.fuel_maker)
.bind(&self.if_staked_gov_token_amount)
.bind(self.last_fuel_if_bonus_update_ts)
.bind(&self.padding)
.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 UserStatsRow {
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 user_stats_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 UserStatsRow {
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 user_stats_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 UserStatsMigrationOperation;
#[async_trait::async_trait]
impl sqlx_migrator::Operation<sqlx::Postgres> for UserStatsMigrationOperation {
async fn up(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(
r#"CREATE TABLE IF NOT EXISTS user_stats_account (
-- Account data
"authority" BYTEA NOT NULL,
"referrer" BYTEA NOT NULL,
"fees" JSONB NOT NULL,
"next_epoch_ts" INT8 NOT NULL,
"maker_volume30d" NUMERIC(20) NOT NULL,
"taker_volume30d" NUMERIC(20) NOT NULL,
"filler_volume30d" NUMERIC(20) NOT NULL,
"last_maker_volume30d_ts" INT8 NOT NULL,
"last_taker_volume30d_ts" INT8 NOT NULL,
"last_filler_volume30d_ts" INT8 NOT NULL,
"if_staked_quote_asset_amount" NUMERIC(20) NOT NULL,
"number_of_sub_accounts" INT4 NOT NULL,
"number_of_sub_accounts_created" INT4 NOT NULL,
"referrer_status" INT2 NOT NULL,
"disable_update_perp_bid_ask_twap" INT2 NOT NULL,
"paused_operations" INT2 NOT NULL,
"fuel_overflow_status" INT2 NOT NULL,
"fuel_insurance" INT8 NOT NULL,
"fuel_deposits" INT8 NOT NULL,
"fuel_borrows" INT8 NOT NULL,
"fuel_positions" INT8 NOT NULL,
"fuel_taker" INT8 NOT NULL,
"fuel_maker" INT8 NOT NULL,
"if_staked_gov_token_amount" NUMERIC(20) NOT NULL,
"last_fuel_if_bonus_update_ts" INT8 NOT NULL,
"padding" BYTEA 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 user_stats_account"#)
.execute(connection)
.await?;
Ok(())
}
}