use core::fmt;
use redis::ToRedisArgs;
use solana_pubkey::Pubkey;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RedisKey(String);
impl RedisKey {
pub fn from_string(s: String) -> Self {
Self(s)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub const VAULT_PREFIX: &'static str = "account:pyra:vault";
pub const WITHDRAW_ORDER_PREFIX: &'static str = "account:pyra:withdraw_order";
pub const SPEND_LIMITS_ORDER_PREFIX: &'static str = "account:pyra:spend_limits_order";
pub const DEPOSIT_ADDRESS_PREFIX: &'static str = "account:pyra:deposit_address";
pub const DEPOSIT_ADDRESS_OWNER_PREFIX: &'static str = "reverse:deposit_address";
pub const USER_WITHDRAW_ORDERS_PREFIX: &'static str = "user:orders:withdraws";
pub const USER_SPEND_LIMITS_ORDERS_PREFIX: &'static str = "user:orders:spend_limits";
pub const TOKEN_ACCOUNT_PREFIX: &'static str = "account:token";
pub const PRICE_PREFIX: &'static str = "price";
pub const ORACLE_PREFIX: &'static str = "oracle";
pub const ORACLE_CACHE_PREFIX: &'static str = "account:oracle";
pub const REGISTERED_WALLETS: &'static str = "registered_wallets";
pub const REGISTERED_WALLETS_CHANGES: &'static str = "registered_wallets:changes";
pub const USER_SOLANA_ADDRESS_PREFIX: &'static str = "user:solana_address";
pub const USER_PROVIDER_PREFIX: &'static str = "user:provider";
pub const PENDING_DEPOSIT_PREFIX: &'static str = "pending_deposits";
pub fn pattern(prefix: &str) -> String {
format!("{prefix}:*")
}
pub fn vault(vault: &Pubkey) -> Self {
let p = Self::VAULT_PREFIX;
Self(format!("{p}:{vault}"))
}
pub fn deposit_address_spl(deposit_address: &Pubkey, mint: &Pubkey) -> Self {
let p = Self::DEPOSIT_ADDRESS_PREFIX;
Self(format!("{p}:{deposit_address}:{mint}"))
}
pub fn deposit_address_sol(deposit_address: &Pubkey) -> Self {
let p = Self::DEPOSIT_ADDRESS_PREFIX;
Self(format!("{p}:{deposit_address}:native"))
}
pub fn deposit_address_owner(deposit_address: &Pubkey) -> Self {
let p = Self::DEPOSIT_ADDRESS_OWNER_PREFIX;
Self(format!("{p}:{deposit_address}"))
}
pub fn withdraw_order(order: &Pubkey) -> Self {
let p = Self::WITHDRAW_ORDER_PREFIX;
Self(format!("{p}:{order}"))
}
pub fn user_withdraw_orders(owner: &Pubkey) -> Self {
let p = Self::USER_WITHDRAW_ORDERS_PREFIX;
Self(format!("{p}:{owner}"))
}
pub fn spend_limits_order(order: &Pubkey) -> Self {
let p = Self::SPEND_LIMITS_ORDER_PREFIX;
Self(format!("{p}:{order}"))
}
pub fn user_spend_limits_orders(owner: &Pubkey) -> Self {
let p = Self::USER_SPEND_LIMITS_ORDERS_PREFIX;
Self(format!("{p}:{owner}"))
}
pub fn token_account(token_account: &Pubkey) -> Self {
let p = Self::TOKEN_ACCOUNT_PREFIX;
Self(format!("{p}:{token_account}"))
}
pub fn price(asset_id: pyra_tokens::AssetId) -> Self {
let p = Self::PRICE_PREFIX;
Self(format!("{p}:{asset_id}"))
}
pub fn oracle(oracle: &Pubkey) -> Self {
let p = Self::ORACLE_PREFIX;
Self(format!("{p}:{oracle}"))
}
pub fn oracle_cache(asset_id: pyra_tokens::AssetId) -> Self {
let p = Self::ORACLE_CACHE_PREFIX;
Self(format!("{p}:{asset_id}"))
}
pub fn registered_wallets() -> Self {
Self(Self::REGISTERED_WALLETS.to_string())
}
pub fn user_solana_address(owner: &str) -> Self {
let p = Self::USER_SOLANA_ADDRESS_PREFIX;
Self(format!("{p}:{owner}"))
}
pub fn user_provider(user_id: &str) -> Self {
let p = Self::USER_PROVIDER_PREFIX;
Self(format!("{p}:{user_id}"))
}
pub fn pending_deposit(owner: &str, mint: &str) -> Self {
let p = Self::PENDING_DEPOSIT_PREFIX;
Self(format!("{p}:{owner}:{mint}"))
}
}
impl fmt::Display for RedisKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl ToRedisArgs for RedisKey {
fn write_redis_args<W: ?Sized + redis::RedisWrite>(&self, out: &mut W) {
out.write_arg(self.0.as_bytes());
}
}
impl AsRef<str> for RedisKey {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod tests {
use super::*;
use pyra_tokens::AssetId;
use std::str::FromStr;
#[test]
fn vault_key_format() {
let pk = Pubkey::from_str("11111111111111111111111111111111").unwrap();
let key = RedisKey::vault(&pk);
assert_eq!(
key.to_string(),
"account:pyra:vault:11111111111111111111111111111111"
);
}
#[test]
fn deposit_address_spl_key_format() {
let addr = Pubkey::from_str("11111111111111111111111111111111").unwrap();
let mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap();
let key = RedisKey::deposit_address_spl(&addr, &mint);
assert!(key.to_string().starts_with("account:pyra:deposit_address:"));
assert!(key
.to_string()
.contains("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));
}
#[test]
fn deposit_address_sol_key_format() {
let addr = Pubkey::from_str("11111111111111111111111111111111").unwrap();
let key = RedisKey::deposit_address_sol(&addr);
assert!(key.to_string().ends_with(":native"));
}
#[test]
fn price_key_format() {
let key = RedisKey::price(AssetId::USDC);
assert_eq!(key.to_string(), "price:0");
}
#[test]
fn price_key_uses_asset_id() {
let key = RedisKey::price(AssetId::PYUSD);
assert_eq!(key.to_string(), "price:6");
}
#[test]
fn oracle_cache_key_uses_asset_id() {
let key = RedisKey::oracle_cache(AssetId::PYUSD);
assert_eq!(key.to_string(), "account:oracle:6");
}
#[test]
fn pattern_format() {
let pat = RedisKey::pattern(RedisKey::VAULT_PREFIX);
assert_eq!(pat, "account:pyra:vault:*");
}
#[test]
fn user_keys_format() {
let key = RedisKey::user_solana_address("abc123");
assert_eq!(key.to_string(), "user:solana_address:abc123");
let key = RedisKey::user_provider("did:privy:abc");
assert_eq!(key.to_string(), "user:provider:did:privy:abc");
}
#[test]
fn pending_deposit_format() {
let key = RedisKey::pending_deposit("owner1", "mint1");
assert_eq!(key.to_string(), "pending_deposits:owner1:mint1");
}
#[test]
fn redis_key_as_ref() {
let key = RedisKey::price(AssetId::WSOL);
let s: &str = key.as_ref();
assert_eq!(s, "price:1");
}
}