Skip to content

Commit

Permalink
feat: impl network management + some refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
MishkaRogachev committed Sep 29, 2024
1 parent ec20db5 commit 817416e
Show file tree
Hide file tree
Showing 28 changed files with 552 additions and 156 deletions.
16 changes: 13 additions & 3 deletions src/core/chain.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum Chain {
EthereumMainnet,
EthereumSepolia,
Expand All @@ -10,8 +9,19 @@ pub enum Chain {
ArbitrumSepolia,
}

pub const MAINNET_CHAINS: [Chain; 3] = [
Chain::EthereumMainnet,
Chain::OptimismMainnet,
Chain::ArbitrumMainnet,
];

pub const TESTNET_CHAINS: [Chain; 3] = [
Chain::EthereumSepolia,
Chain::OptimismSepolia,
Chain::ArbitrumSepolia,
];

impl Chain {
#[allow(dead_code)]
pub fn get_display_name(&self) -> &str {
match self {
Chain::EthereumMainnet => "Ethereum Mainnet",
Expand Down
47 changes: 47 additions & 0 deletions src/persistence/db_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::core::{key_pair::KeyPair, seed_phrase::SeedPhrase};
use super::db::Db;

const ROOT_KEYPAIR: &[u8] = b"root_keypair";
const ROOT_SEED_PHRASE: &[u8] = b"root_seed_phrase";

const ERR_SEED_PHRASE_NOT_FOUND: &str = "Seed phrase not found";
const ERR_KEYPAIR_NOT_FOUND: &str = "Keypair not found";

impl Db {
pub fn save_seed_phrase(&self, seed_phrase: &SeedPhrase) -> anyhow::Result<()> {
let words = seed_phrase.get_words();
let serialized_seed_phrase = serde_json::to_vec(&words)?;
self.insert(ROOT_SEED_PHRASE, &serialized_seed_phrase)
}

pub fn get_seed_phrase(&self) -> anyhow::Result<SeedPhrase> {
let serialized_seed_phrase: Option<Vec<u8>> = self.get(ROOT_SEED_PHRASE)?;
if let Some(serialized_seed_phrase) = serialized_seed_phrase {
let words: Vec<String> = serde_json::from_slice(&serialized_seed_phrase)?;
return SeedPhrase::from_words(words);
}
Err(anyhow::anyhow!(ERR_SEED_PHRASE_NOT_FOUND))
}

pub fn delete_seed_phrase(&self) -> anyhow::Result<()> {
match self.remove(ROOT_SEED_PHRASE) {
Ok(_) => Ok(()),
Err(_) => Err(anyhow::anyhow!(ERR_SEED_PHRASE_NOT_FOUND))
}
}

pub fn save_keypair(&self, keypair: &KeyPair) -> anyhow::Result<()> {
let serialized_keypair = serde_json::to_vec(&keypair)?;
self.insert(ROOT_KEYPAIR, &serialized_keypair)
}

pub fn get_keypair(&self) -> anyhow::Result<KeyPair> {
let serialized_keypair: Option<Vec<u8>> = self.get(ROOT_KEYPAIR)?;
if let Some(serialized_keypair) = serialized_keypair {
let keypair: KeyPair = serde_json::from_slice(&serialized_keypair)?;
keypair.validate()?;
return Ok(keypair);
}
Err(anyhow::anyhow!(ERR_KEYPAIR_NOT_FOUND))
}
}
20 changes: 20 additions & 0 deletions src/persistence/db_chains.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::core::chain::Chain;
use super::db::Db;

const ACTIVE_NETWORKS: &[u8] = b"active_networks";

impl Db {
pub fn save_active_networks(&self, chains: &[Chain]) -> anyhow::Result<()> {
let serialized_chains = serde_json::to_vec(chains)?;
self.insert(ACTIVE_NETWORKS, &serialized_chains)
}

pub fn get_active_networks(&self) -> anyhow::Result<Vec<Chain>> {
let serialized_chains: Option<Vec<u8>> = self.get(ACTIVE_NETWORKS)?;
if let Some(serialized_chains) = serialized_chains {
let chains: Vec<Chain> = serde_json::from_slice(&serialized_chains)?;
return Ok(chains);
}
Ok(vec![])
}
}
2 changes: 2 additions & 0 deletions src/persistence/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod cipher;
mod cipher_test;
pub mod db;
pub mod db_accounts;
pub mod db_chains;
mod db_test;
pub mod manage;
mod manage_test;
42 changes: 23 additions & 19 deletions src/service/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,57 @@
use std::collections::HashMap;
use std::{collections::HashMap, sync::Arc};

use crate::core::{chain::Chain, provider::{Provider, Balance}};
use crate::{core::{chain::Chain, provider::{Balance, Provider}}, persistence::db::Db};

#[derive(Clone)]
pub struct Crypto {
db: Arc<Db>,
endpoint_url: String,
providers: HashMap<Chain, Provider>,
}

impl Crypto {
pub fn new(endpoint_url: &str) -> Self {
pub fn new(db: Arc<Db>, endpoint_url: &str) -> Self {
Self {
db,
endpoint_url: endpoint_url.to_string(),
providers: HashMap::new()
}
}

pub fn add_chain(&mut self, chain: Chain) -> anyhow::Result<bool> {
if self.providers.contains_key(&chain) {
return Ok(false);
}
let provider = Provider::new(&self.endpoint_url, chain.clone())?;
self.providers.insert(chain, provider);
Ok(true)
}

#[allow(dead_code)]
pub fn set_chains(&mut self, chains: Vec<Chain>) -> anyhow::Result<()> {
fn set_active_networks_impl(&mut self, chains: Vec<Chain>) -> anyhow::Result<()> {
let old_chains = self.providers.keys().cloned().collect::<Vec<_>>();
for chain in &old_chains {
if !chains.contains(&chain) {
self.providers.remove(&chain);
}
}

for chain in &chains {
if !old_chains.contains(chain) {
self.add_chain(chain.clone())?;
let provider = Provider::new(&self.endpoint_url, chain.clone())?;
self.providers.insert(chain.clone(), provider);
}
}

Ok(())
}

#[allow(dead_code)]
pub fn get_active_chains(&self) -> Vec<Chain> {
pub fn save_active_networks(&mut self, chains: Vec<Chain>) -> anyhow::Result<()> {
self.set_active_networks_impl(chains.clone())?;
self.db.save_active_networks(&chains)
}

pub fn load_active_networks(&mut self) -> anyhow::Result<()> {
let chains = self.db.get_active_networks()?;
self.set_active_networks_impl(chains)
}

pub fn get_active_networks(&self) -> Vec<Chain> {
self.providers.keys().cloned().collect()
}

pub fn in_testnet(&self) -> bool {
self.providers.keys().any(|chain| chain.is_test_network())
}

pub async fn get_eth_balance(&self, account: web3::types::Address) -> anyhow::Result<Balance> {
let mut summary= Balance::new(0.0, 0.0, "ETH", false);
for provider in self.providers.values() {
Expand Down
34 changes: 24 additions & 10 deletions src/service/crypto_test.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
#[cfg(test)]
mod tests {
use crate::core::chain::Chain;
use std::sync::Arc;

use crate::{core::chain::Chain, persistence::db::Db};
use super::super::crypto::Crypto;

fn create_test_db() -> anyhow::Result<Db> {
let db_name = format!("test_raclette_crypto_{}", uuid::Uuid::new_v4().to_string());
let mut path = std::env::temp_dir();
path.push(db_name);

let config = sled::Config::new().temporary(true).path(path);
let db = config.open()?;

Db::open(db, "12345678")
}

#[test]
fn test_crypto_chains() -> anyhow::Result<()> {
let mut crypto = Crypto::new("http://localhost:8545");
assert_eq!(crypto.get_active_chains().len(), 0);
let db = Arc::new(create_test_db()?);
let mut crypto = Crypto::new(db, "http://localhost:8545");
assert_eq!(crypto.get_active_networks().len(), 0);

crypto.add_chain(Chain::EthereumMainnet)?;
assert_eq!(crypto.get_active_chains().len(), 1);
crypto.load_active_networks()?;
assert_eq!(crypto.get_active_networks().len(), 0);

crypto.set_chains(vec![Chain::EthereumMainnet, Chain::ArbitrumMainnet])?;
assert_eq!(crypto.get_active_chains().len(), 2);
crypto.save_active_networks(vec![Chain::EthereumMainnet, Chain::ArbitrumMainnet])?;
assert_eq!(crypto.get_active_networks().len(), 2);

assert!(crypto.get_active_chains().contains(&Chain::EthereumMainnet));
assert!(crypto.get_active_chains().contains(&Chain::ArbitrumMainnet));
assert!(!crypto.get_active_chains().contains(&Chain::OptimismSepolia));
assert!(crypto.get_active_networks().contains(&Chain::EthereumMainnet));
assert!(crypto.get_active_networks().contains(&Chain::ArbitrumMainnet));
assert!(!crypto.get_active_networks().contains(&Chain::OptimismSepolia));

Ok(())
}
Expand Down
56 changes: 10 additions & 46 deletions src/service/session.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
use std::sync::Arc;

use crate::core::{key_pair::KeyPair, seed_phrase::SeedPhrase};
use crate::core::{chain, key_pair::KeyPair, seed_phrase::SeedPhrase};
use crate::persistence::{db::Db, manage};

const ROOT_KEYPAIR: &[u8] = b"root_keypair";
const ROOT_SEED_PHRASE: &[u8] = b"root_seed_phrase";

const ERR_SEED_PHRASE_NOT_FOUND: &str = "Seed phrase not found";
const ERR_ACCOUNT_NOT_FOUND: &str = "Account not found";
const ERR_WRONG_PASSWORD_PROVIDED: &str = "Wrong password provided";

#[derive(Clone)]
pub struct Session {
pub account: web3::types::Address,
db: Arc<Db>
pub db: Arc<Db>
}

impl Session {
Expand All @@ -25,12 +20,9 @@ impl Session {
let account = keypair.get_address();
let db = manage::open_database(&db_path()?, account, password)?;

let words = seed_phrase.get_words();
let serialized_seed_phrase = serde_json::to_vec(&words)?;
db.insert(ROOT_SEED_PHRASE, &serialized_seed_phrase)?;

let serialized_keypair = serde_json::to_vec(&keypair)?;
db.insert(ROOT_KEYPAIR, &serialized_keypair)?;
db.save_seed_phrase(seed_phrase)?;
db.save_keypair(&keypair)?;
db.save_active_networks(&chain::MAINNET_CHAINS)?;

Ok(Session {
account,
Expand All @@ -40,16 +32,14 @@ impl Session {

pub fn login(account: web3::types::Address, password: &str) -> anyhow::Result<Self> {
let db = manage::open_database(&db_path()?, account, password)?;
let session = Session {
account,
db: Arc::new(db),
};

if session.get_keypair().is_err() {
if db.get_keypair().is_err() {
return Err(anyhow::anyhow!(ERR_WRONG_PASSWORD_PROVIDED));
}

Ok(session)
Ok(Session {
account,
db: Arc::new(db),
})
}

pub fn list_accounts() -> anyhow::Result<Vec<web3::types::Address>> {
Expand All @@ -60,32 +50,6 @@ impl Session {
manage::remove_database(&db_path()?, account)
}

pub fn get_seed_phrase(&self) -> anyhow::Result<SeedPhrase> {
let serialized_seed_phrase: Option<Vec<u8>> = self.db.get(ROOT_SEED_PHRASE)?;
if let Some(serialized_seed_phrase) = serialized_seed_phrase {
let words: Vec<String> = serde_json::from_slice(&serialized_seed_phrase)?;
return SeedPhrase::from_words(words);
}
Err(anyhow::anyhow!(ERR_SEED_PHRASE_NOT_FOUND))
}

pub fn get_keypair(&self) -> anyhow::Result<KeyPair> {
let serialized_keypair: Option<Vec<u8>> = self.db.get(ROOT_KEYPAIR)?;
if let Some(serialized_keypair) = serialized_keypair {
let keypair: KeyPair = serde_json::from_slice(&serialized_keypair)?;
keypair.validate()?;
return Ok(keypair);
}
Err(anyhow::anyhow!(ERR_ACCOUNT_NOT_FOUND))
}

pub fn delete_seed_phrase(&self) -> anyhow::Result<()> {
match self.db.remove(ROOT_SEED_PHRASE) {
Ok(_) => Ok(()),
Err(_) => Err(anyhow::anyhow!(ERR_SEED_PHRASE_NOT_FOUND))
}
}

pub fn delete_account(&self) -> anyhow::Result<()> {
Self::remove_account(self.account)
}
Expand Down
8 changes: 4 additions & 4 deletions src/service/session_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ mod tests {
assert!(Session::login(account, "wrong_password").is_err());

let session = Session::login(account, password)?;
let keypair_back = session.get_keypair()?;
let keypair_back = session.db.get_keypair()?;
keypair_back.validate()?;
assert_eq!(keypair, keypair_back);

let accounts = Session::list_accounts()?;
assert!(accounts.contains(&account));

// Access seed phrase
let seed_phrase_back = session.get_seed_phrase()?;
let seed_phrase_back = session.db.get_seed_phrase()?;
assert_eq!(seed_phrase, seed_phrase_back);

// Delete seed phrase
session.delete_seed_phrase()?;
assert!(session.get_seed_phrase().is_err());
session.db.delete_seed_phrase()?;
assert!(session.db.get_seed_phrase().is_err());

// Remove account
session.delete_account()?;
Expand Down
10 changes: 5 additions & 5 deletions src/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub const MAX_APP_WIDTH: u16 = 120;

#[async_trait::async_trait]
pub trait AppScreen {
fn handle_event(&mut self, event: Event) -> anyhow::Result<bool>;
async fn handle_event(&mut self, event: Event) -> anyhow::Result<bool>;
async fn update(&mut self);
fn render(&mut self, frame: &mut Frame, area: Rect);
}
Expand All @@ -37,9 +37,9 @@ impl App {
Ok(Self { shutdown_handle, current_screen, command_rx, events })
}

pub fn process_events(&mut self) {
pub async fn process_events(&mut self) {
if let Ok(event) = self.events.try_recv() {
self.handle_event(event).expect("Failed to handle screen event");
self.handle_event(event).await.expect("Failed to handle screen event");
}

if let Ok(command) = self.command_rx.try_recv() {
Expand All @@ -57,8 +57,8 @@ impl App {

#[async_trait::async_trait]
impl AppScreen for App {
fn handle_event(&mut self, event: Event) -> anyhow::Result<bool> {
self.current_screen.handle_event(event)
async fn handle_event(&mut self, event: Event) -> anyhow::Result<bool> {
self.current_screen.handle_event(event).await
}

async fn update(&mut self) {
Expand Down
3 changes: 2 additions & 1 deletion src/tui/popups/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod receive;
pub mod networks;
pub mod receive;
Loading

0 comments on commit 817416e

Please sign in to comment.