Skip to content

Commit

Permalink
feat: withdraws status screen (#17)
Browse files Browse the repository at this point in the history
* work

* suggestions

* fmt

* refactoring

* suggestions

* sorting

* general info grid

* more work

* pagination

* fmt

* suggestions

* custom items per page

* Delete testnet_nodes.yml
  • Loading branch information
ogabrielides authored Oct 25, 2024
1 parent 2664c03 commit 1c720c2
Show file tree
Hide file tree
Showing 8 changed files with 634 additions and 10 deletions.
Binary file added icons/withdraws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::ui::dpns_contested_names_screen::{DPNSContestedNamesScreen, DPNSSubsc
use crate::ui::identities::identities_screen::IdentitiesScreen;
use crate::ui::network_chooser_screen::NetworkChooserScreen;
use crate::ui::transition_visualizer_screen::TransitionVisualizerScreen;
use crate::ui::withdraws_status_screen::WithdrawsStatusScreen;
use crate::ui::{MessageType, RootScreenType, Screen, ScreenLike, ScreenType};
use dash_sdk::dpp::dashcore::Network;
use derive_more::From;
Expand Down Expand Up @@ -125,6 +126,7 @@ impl AppState {
let mut transition_visualizer_screen =
TransitionVisualizerScreen::new(&mainnet_app_context);
let mut document_query_screen = DocumentQueryScreen::new(&mainnet_app_context);
let mut withdraws_status_screen = WithdrawsStatusScreen::new(&mainnet_app_context);
let mut network_chooser_screen = NetworkChooserScreen::new(
&mainnet_app_context,
testnet_app_context.as_ref(),
Expand All @@ -149,6 +151,7 @@ impl AppState {
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Owned);
transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context);
document_query_screen = DocumentQueryScreen::new(testnet_app_context);
withdraws_status_screen = WithdrawsStatusScreen::new(testnet_app_context);
}
network_chooser_screen.current_network = chosen_network;
}
Expand Down Expand Up @@ -185,6 +188,10 @@ impl AppState {
RootScreenType::RootScreenDocumentQuery,
Screen::DocumentQueryScreen(document_query_screen),
),
(
RootScreenType::RootScreenWithdrawsStatus,
Screen::WithdrawsStatusScreen(withdraws_status_screen),
),
(
RootScreenType::RootScreenNetworkChooser,
Screen::NetworkChooserScreen(network_chooser_screen),
Expand Down Expand Up @@ -297,6 +304,9 @@ impl App for AppState {
BackendTaskSuccessResult::SuccessfulVotes(_) => {
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::WithdrawalStatus(_) => {
self.visible_screen_mut().display_task_result(message);
}
},
TaskResult::Error(message) => {
self.visible_screen_mut()
Expand Down
6 changes: 6 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct AppContext {
pub(crate) sdk: Sdk,
pub(crate) config: NetworkConfig,
pub(crate) dpns_contract: Arc<DataContract>,
pub(crate) withdraws_contract: Arc<DataContract>,
pub(crate) core_client: Client,
pub(crate) has_wallet: AtomicBool,
pub(crate) wallets: RwLock<Vec<Arc<RwLock<Wallet>>>>,
Expand Down Expand Up @@ -56,6 +57,10 @@ impl AppContext {
load_system_data_contract(SystemDataContract::DPNS, PlatformVersion::latest())
.expect("expected to load dpns contract");

let withdrawal_contract =
load_system_data_contract(SystemDataContract::Withdrawals, PlatformVersion::latest())
.expect("expected to get withdrawal contract");

let addr = format!(
"http://{}:{}",
network_config.core_host, network_config.core_rpc_port
Expand Down Expand Up @@ -84,6 +89,7 @@ impl AppContext {
sdk,
config: network_config,
dpns_contract: Arc::new(dpns_contract),
withdraws_contract: Arc::new(withdrawal_contract),
core_client,
has_wallet: (!wallets.is_empty()).into(),
wallets: RwLock::new(wallets),
Expand Down
7 changes: 7 additions & 0 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::platform::contract::ContractTask;
use crate::platform::core::{CoreItem, CoreTask};
use crate::platform::document::DocumentTask;
use crate::platform::identity::IdentityTask;
use crate::platform::withdrawals::{WithdrawStatusData, WithdrawalsTask};
use dash_sdk::dpp::voting::votes::Vote;
use dash_sdk::query_types::Documents;
use std::sync::Arc;
Expand All @@ -15,6 +16,7 @@ pub mod contract;
pub mod core;
mod document;
pub mod identity;
pub mod withdrawals;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum BackendTask {
Expand All @@ -23,6 +25,7 @@ pub(crate) enum BackendTask {
ContractTask(ContractTask),
ContestedResourceTask(ContestedResourceTask),
CoreTask(CoreTask),
WithdrawalTask(WithdrawalsTask),
}

#[derive(Debug, Clone, PartialEq)]
Expand All @@ -32,6 +35,7 @@ pub(crate) enum BackendTaskSuccessResult {
Documents(Documents),
CoreItem(CoreItem),
SuccessfulVotes(Vec<Vote>),
WithdrawalStatus(WithdrawStatusData),
}

impl BackendTaskSuccessResult {}
Expand Down Expand Up @@ -71,6 +75,9 @@ impl AppContext {
self.run_document_task(document_task, &sdk).await
}
BackendTask::CoreTask(core_task) => self.run_core_task(core_task).await,
BackendTask::WithdrawalTask(withdrawal_task) => {
self.run_withdraws_task(withdrawal_task, &sdk).await
}
}
}
}
211 changes: 211 additions & 0 deletions src/platform/withdrawals/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
use std::sync::Arc;
use chrono::{DateTime, Local, Utc};
use dash_sdk::dashcore_rpc::dashcore::{Address, Network, ScriptBuf};
use dash_sdk::dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal::properties::{AMOUNT, OUTPUT_SCRIPT, STATUS};
use dash_sdk::dpp::data_contracts::withdrawals_contract::WithdrawalStatus;
use dash_sdk::dpp::document::DocumentV0Getters;
use dash_sdk::dpp::fee::Credits;
use dash_sdk::dpp::platform_value::btreemap_extensions::BTreeValueMapHelper;
use dash_sdk::dpp::platform_value::Value;
use dash_sdk::dpp::version::PlatformVersion;
use dash_sdk::dpp::withdrawal::daily_withdrawal_limit::daily_withdrawal_limit;
use dash_sdk::drive::drive::RootTree;
use dash_sdk::drive::grovedb::Element;
use dash_sdk::drive::grovedb::element::SumValue;
use dash_sdk::drive::query::{OrderClause, WhereClause, WhereOperator};
use dash_sdk::platform::{Document, DocumentQuery, FetchMany, Identifier};
use dash_sdk::platform::fetch_current_no_parameters::FetchCurrent;
use dash_sdk::query_types::{KeysInPath, TotalCreditsInPlatform};
use dash_sdk::Sdk;
use crate::context::AppContext;
use crate::platform::BackendTaskSuccessResult;

/// constant id for subtree containing the sum of withdrawals
pub const WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY: [u8; 1] = [2];

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum WithdrawalsTask {
QueryWithdrawals(Vec<Value>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct WithdrawRecord {
pub date_time: DateTime<Local>,
pub status: WithdrawalStatus,
pub amount: Credits,
pub owner_id: Identifier,
pub address: Address,
}

#[derive(Debug, Clone, PartialEq)]
pub struct WithdrawStatusData {
pub withdrawals: Vec<WithdrawRecord>,
pub total_amount: Credits,
pub recent_withdrawal_amounts: SumValue,
pub daily_withdrawal_limit: Credits,
pub total_credits_on_platform: Credits,
}

impl AppContext {
pub async fn run_withdraws_task(
self: &Arc<Self>,
task: WithdrawalsTask,
sdk: &Sdk,
) -> Result<BackendTaskSuccessResult, String> {
let sdk = sdk.clone();
match &task {
WithdrawalsTask::QueryWithdrawals(status_filter) => {
self.query_withdrawals(sdk, status_filter).await
}
}
}

pub(super) async fn query_withdrawals(
self: &Arc<Self>,
sdk: Sdk,
status_filter: &Vec<Value>,
) -> Result<BackendTaskSuccessResult, String> {
let queued_document_query = DocumentQuery {
data_contract: self.withdraws_contract.clone(),
document_type_name: "withdrawal".to_string(),
where_clauses: vec![WhereClause {
field: "status".to_string(),
operator: WhereOperator::In,
value: Value::Array(status_filter.to_vec()),
}],
order_by_clauses: vec![
OrderClause {
field: "status".to_string(),
ascending: true,
},
OrderClause {
field: "transactionIndex".to_string(),
ascending: true,
},
],
limit: 100,
start: None,
};

let documents = Document::fetch_many(&sdk, queued_document_query.clone())
.await
.map_err(|e| e.to_string())?;

let keys_in_path = KeysInPath {
path: vec![vec![RootTree::WithdrawalTransactions as u8]],
keys: vec![WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY.to_vec()],
};

let elements = Element::fetch_many(&sdk, keys_in_path)
.await
.map_err(|e| e.to_string())?;

let sum_tree_element_option = elements
.get(&WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY.to_vec())
.ok_or_else(|| {
"could not get sum tree value for current withdrawal maximum".to_string()
})?;

let sum_tree_element = sum_tree_element_option.as_ref().ok_or_else(|| {
"could not get sum tree value for current withdrawal maximum".to_string()
})?;

let value = if let Element::SumTree(_, value, _) = sum_tree_element {
value
} else {
return Err("could not get sum tree value for current withdrawal maximum".to_string());
};

let total_credits = TotalCreditsInPlatform::fetch_current(&sdk)
.await
.map_err(|e| e.to_string())?;

Ok(BackendTaskSuccessResult::WithdrawalStatus(
util_transform_documents_to_withdrawal_status(
*value,
total_credits.0,
&documents.values().filter_map(|a| a.clone()).collect(),
sdk.network,
)?,
))
}
}

fn util_transform_documents_to_withdrawal_status(
recent_withdrawal_amounts: SumValue,
total_credits_on_platform: Credits,
withdrawal_documents: &Vec<Document>,
network: Network,
) -> Result<WithdrawStatusData, String> {
let total_amount = withdrawal_documents.iter().try_fold(0, |acc, document| {
document
.properties()
.get_integer::<Credits>(AMOUNT)
.map_err(|_| "expected amount on withdrawal".to_string())
.map(|amount| acc + amount)
})?;

let daily_withdrawal_limit =
daily_withdrawal_limit(total_credits_on_platform, PlatformVersion::latest())
.map_err(|_| "expected to get daily withdrawal limit".to_string())?;

let mut vec_withdraws = vec![];
for document in withdrawal_documents {
vec_withdraws.push(util_convert_document_to_record(document, network)?);
}

Ok(WithdrawStatusData {
withdrawals: vec_withdraws,
total_amount,
recent_withdrawal_amounts,
daily_withdrawal_limit,
total_credits_on_platform,
})
}

fn util_convert_document_to_record(
document: &Document,
network: Network,
) -> Result<WithdrawRecord, String> {
let index = document
.created_at()
.ok_or_else(|| "expected created at".to_string())?;

let local_datetime: DateTime<Local> = DateTime::<Utc>::from_timestamp_millis(index as i64)
.ok_or_else(|| "expected date time".to_string())?
.into();

let amount = document
.properties()
.get_integer::<Credits>(AMOUNT)
.map_err(|_| "expected amount on withdrawal".to_string())?;

let status_int = document
.properties()
.get_integer::<u8>(STATUS)
.map_err(|_| "expected status on withdrawal".to_string())?;

let status: WithdrawalStatus = status_int
.try_into()
.map_err(|_| "invalid withdrawal status".to_string())?;

let owner_id = document.owner_id();

let address_bytes = document
.properties()
.get_bytes(OUTPUT_SCRIPT)
.map_err(|_| "expected output script".to_string())?;

let output_script = ScriptBuf::from_bytes(address_bytes);

let address = Address::from_script(&output_script, network)
.map_err(|_| "expected a valid address".to_string())?;

Ok(WithdrawRecord {
date_time: local_datetime,
status,
amount,
owner_id,
address,
})
}
5 changes: 5 additions & 0 deletions src/ui/components/left_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ pub fn add_left_panel(
RootScreenType::RootScreenTransitionVisualizerScreen,
"tools.png",
),
(
"W",
RootScreenType::RootScreenWithdrawsStatus,
"withdraws.png",
),
("N", RootScreenType::RootScreenNetworkChooser, "config.png"),
];

Expand Down
Loading

0 comments on commit 1c720c2

Please sign in to comment.