Auto tier change (#184)

* lint

* change user to premium on admin credits

todo: change on other deposits

* set tier more places

* BadRequest instead of 500

* insert existing users too

* add the premium file
This commit is contained in:
Bryan Stitt 2023-07-12 10:24:16 -07:00 committed by GitHub
parent b6cbf02ae7
commit 5d207fb2c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 108 additions and 54 deletions

@ -4,7 +4,7 @@ use crate::serialization;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, Hash, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]

@ -62,6 +62,8 @@ impl Balance {
}
pub fn was_ever_premium(&self) -> bool {
// TODO: technically we should also check that user_tier.downgrade_tier_id.is_some()
// but now we set premium automatically on deposit, so its fine for now
self.user_id != 0 && self.total_deposits() >= Decimal::from(10)
}

@ -4,7 +4,7 @@ use crate::frontend::authorization::{AuthorizationChecks, RpcSecretKey};
use derive_more::From;
use entities::rpc_key;
use migration::sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use moka::future::{Cache, ConcurrentCacheExt};
use moka::future::Cache;
use std::fmt;
use std::net::IpAddr;
use std::sync::Arc;

@ -6,6 +6,7 @@ use crate::app::Web3ProxyApp;
use crate::errors::Web3ProxyResponse;
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext};
use crate::frontend::users::authentication::PostLogin;
use crate::premium::{get_user_and_tier_from_address, grant_premium_tier};
use crate::user_token::UserBearerToken;
use axum::{
extract::{Path, Query},
@ -65,14 +66,16 @@ pub async fn admin_increase_balance(
.await?
.ok_or_else(|| Web3ProxyError::AccessDenied("not an admin".into()))?;
let user_entry: user::Model = user::Entity::find()
.filter(user::Column::Address.eq(payload.user_address.as_bytes()))
.one(&txn)
let (user_entry, user_tier_entry) = get_user_and_tier_from_address(&payload.user_address, &txn)
.await?
.ok_or(Web3ProxyError::BadRequest(
format!("No user found with {:?}", payload.user_address).into(),
))?;
grant_premium_tier(&user_entry, user_tier_entry.as_ref(), &txn)
.await
.web3_context("granting premium tier")?;
let increase_balance_receipt = admin_increase_balance_receipt::ActiveModel {
amount: sea_orm::Set(payload.amount),
admin_id: sea_orm::Set(admin_entry.id),
@ -81,6 +84,7 @@ pub async fn admin_increase_balance(
..Default::default()
};
increase_balance_receipt.save(&txn).await?;
txn.commit().await?;
// Invalidate the user_balance_cache for this user:

@ -1,10 +1,11 @@
use crate::app::Web3ProxyApp;
use crate::balance::Balance;
use crate::errors::{Web3ProxyError, Web3ProxyResponse, Web3ProxyResult};
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse, Web3ProxyResult};
use crate::frontend::authorization::{
login_is_authorized, Authorization as Web3ProxyAuthorization,
};
use crate::frontend::users::authentication::register_new_user;
use crate::premium::grant_premium_tier;
use anyhow::Context;
use axum::{
extract::Path,
@ -16,7 +17,7 @@ use axum_client_ip::InsecureClientIp;
use axum_macros::debug_handler;
use entities::{
admin_increase_balance_receipt, increase_on_chain_balance_receipt,
stripe_increase_balance_receipt, user,
stripe_increase_balance_receipt, user, user_tier,
};
use ethers::abi::AbiEncode;
use ethers::types::{Address, Block, TransactionReceipt, TxHash, H256};
@ -302,6 +303,8 @@ pub async fn user_balance_post(
// TODO: check bloom filters
let mut user_ids_need_premium = HashSet::new();
// the transaction might contain multiple relevant logs. collect them all
let mut response_data = vec![];
let mut user_ids_to_invalidate = HashSet::new();
@ -405,9 +408,21 @@ pub async fn user_balance_post(
debug!("deposit data: {:#?}", x);
response_data.push(x);
user_ids_need_premium.insert(recipient);
}
}
for user in user_ids_need_premium.into_iter() {
let user_tier = user_tier::Entity::find_by_id(user.user_tier_id)
.one(&txn)
.await?;
grant_premium_tier(&user, user_tier.as_ref(), &txn)
.await
.web3_context("granting premium tier")?;
}
txn.commit().await?;
for user_id in user_ids_to_invalidate.into_iter() {

@ -1,5 +1,6 @@
use crate::app::Web3ProxyApp;
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse};
use crate::premium::grant_premium_tier;
use anyhow::Context;
use axum::{
headers::{authorization::Bearer, Authorization},
@ -7,7 +8,7 @@ use axum::{
Extension, Json, TypedHeader,
};
use axum_macros::debug_handler;
use entities::{stripe_increase_balance_receipt, user};
use entities::{stripe_increase_balance_receipt, user, user_tier};
use ethers::types::Address;
use http::HeaderMap;
use migration::sea_orm::prelude::Decimal;
@ -163,6 +164,14 @@ pub async fn user_balance_stripe_post(
Some(recipient) => {
let _ = insert_receipt_model.save(&txn).await;
let user_tier = user_tier::Entity::find_by_id(recipient.user_tier_id)
.one(&txn)
.await?;
grant_premium_tier(&recipient, user_tier.as_ref(), &txn)
.await
.web3_context("granting premium tier")?;
txn.commit().await?;
// Finally invalidate the cache as well

@ -14,6 +14,7 @@ pub mod frontend;
pub mod http_params;
pub mod jsonrpc;
pub mod pagerduty;
pub mod premium;
pub mod prometheus;
pub mod referral_code;
pub mod relational_db;

64
web3_proxy/src/premium.rs Normal file

@ -0,0 +1,64 @@
use crate::errors::Web3ProxyResult;
use anyhow::Context;
use entities::{user, user_tier};
use ethers::prelude::Address;
use migration::sea_orm::{
self, ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel,
QueryFilter,
};
use tracing::info;
pub async fn get_user_and_tier_from_address(
user_address: &Address,
txn: &DatabaseTransaction,
) -> Web3ProxyResult<Option<(user::Model, Option<user_tier::Model>)>> {
let x = user::Entity::find()
.filter(user::Column::Address.eq(user_address.as_bytes()))
.find_also_related(user_tier::Entity)
.one(txn)
.await?;
Ok(x)
}
pub async fn get_user_and_tier_from_id(
user_id: u64,
txn: &DatabaseTransaction,
) -> Web3ProxyResult<Option<(user::Model, Option<user_tier::Model>)>> {
let x = user::Entity::find_by_id(user_id)
.find_also_related(user_tier::Entity)
.one(txn)
.await?;
Ok(x)
}
/// TODO: improve this so that funding an account that has an "unlimited" key is left alone
pub async fn grant_premium_tier(
user: &user::Model,
user_tier: Option<&user_tier::Model>,
txn: &DatabaseTransaction,
) -> Web3ProxyResult<()> {
if user_tier.is_none() || user_tier.and_then(|x| x.downgrade_tier_id).is_none() {
if user_tier.map(|x| x.title.as_str()) == Some("Premium") {
// user is already premium
} else {
info!("upgrading {} to Premium", user.id);
// switch the user to the premium tier
let new_user_tier = user_tier::Entity::find()
.filter(user_tier::Column::Title.like("Premium"))
.one(txn)
.await?
.context("premium tier not found")?;
let mut user = user.clone().into_active_model();
user.user_tier_id = sea_orm::Set(new_user_tier.id);
user.save(txn).await?;
}
}
Ok(())
}

@ -5,7 +5,7 @@ use std::time::Duration;
use crate::common::admin_increases_balance::admin_increase_balance;
use crate::common::create_admin::create_user_as_admin;
use crate::common::create_user::{create_user, set_user_tier};
use crate::common::create_user::create_user;
use crate::common::user_balance::user_get_balance;
use crate::common::TestApp;
use migration::sea_orm::prelude::Decimal;
@ -39,8 +39,6 @@ async fn test_admin_grant_credits() {
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
info!(?admin_login_response);
set_user_tier(&x, user_login_response.user.clone(), "Premium").await.unwrap();
let increase_balance_response = admin_increase_balance(
&x,
&r,

@ -1,12 +1,8 @@
mod common;
use crate::common::{
admin_increases_balance::admin_increase_balance,
create_admin::create_user_as_admin,
create_user::{create_user, set_user_tier},
rpc_key::user_get_provider,
user_balance::user_get_balance,
TestApp,
admin_increases_balance::admin_increase_balance, create_admin::create_user_as_admin,
create_user::create_user, rpc_key::user_get_provider, user_balance::user_get_balance, TestApp,
};
use ethers::prelude::U64;
use migration::sea_orm::prelude::Decimal;
@ -33,12 +29,6 @@ async fn test_sum_credits_used() {
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// TODO: set the user's user_id to the "Premium" tier
info!("starting balance");
let balance: Balance = user_get_balance(&x, &r, &user_login_response).await;
assert_eq!(

@ -3,7 +3,7 @@ mod common;
use crate::common::admin_deposits::get_admin_deposits;
use crate::common::admin_increases_balance::admin_increase_balance;
use crate::common::create_admin::create_user_as_admin;
use crate::common::create_user::{create_user, set_user_tier};
use crate::common::create_user::create_user;
use crate::common::referral::{
get_referral_code, get_shared_referral_codes, get_used_referral_codes, UserSharedReferralInfo,
UserUsedReferralInfo,
@ -105,10 +105,6 @@ async fn test_admin_balance_increase() {
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// Bump both user's wallet to $20
admin_increase_balance(
&x,
@ -156,10 +152,6 @@ async fn test_user_balance_decreases() {
let admin_login_response = create_user_as_admin(&x, &r, &admin_wallet).await;
let user_login_response = create_user(&x, &r, &user_wallet, None).await;
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// Get the rpc keys for this user
let rpc_keys: RpcKey = user_get_first_rpc_key(&x, &r, &user_login_response).await;
let proxy_endpoint = format!("{}rpc/{}", x.proxy_provider.url(), rpc_keys.secret_key);
@ -267,14 +259,7 @@ async fn test_referral_bonus_non_concurrent() {
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
.await
.unwrap();
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// Bump both user's wallet to $20
// Bump both user's wallet to $20 (which will give them the Premium user tier)
admin_increase_balance(
&x,
&r,
@ -417,13 +402,6 @@ async fn test_referral_bonus_concurrent_referrer_only() {
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
.await
.unwrap();
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// Bump both user's wallet to $20
admin_increase_balance(
&x,
@ -578,13 +556,6 @@ async fn test_referral_bonus_concurrent_referrer_and_user() {
let user_login_response = create_user(&x, &r, &user_wallet, Some(referral_link.clone())).await;
set_user_tier(&x, referrer_login_response.user.clone(), "Premium")
.await
.unwrap();
set_user_tier(&x, user_login_response.user.clone(), "Premium")
.await
.unwrap();
// Bump both user's wallet to $20
admin_increase_balance(
&x,