diff --git a/Cargo.lock b/Cargo.lock index afb7c734f..66b07b1c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.1.53" +version = "1.1.54" dependencies = [ "actix-rt", "aes-gcm", @@ -2814,7 +2814,7 @@ dependencies = [ [[package]] name = "sargon-uniffi" -version = "1.1.53" +version = "1.1.54" dependencies = [ "actix-rt", "assert-json-diff", diff --git a/crates/sargon-uniffi/Cargo.toml b/crates/sargon-uniffi/Cargo.toml index ba21aaf30..39a1f75af 100644 --- a/crates/sargon-uniffi/Cargo.toml +++ b/crates/sargon-uniffi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon-uniffi" # Don't forget to update version in crates/sargon/Cargo.toml -version = "1.1.53" +version = "1.1.54" edition = "2021" build = "build.rs" diff --git a/crates/sargon/Cargo.toml b/crates/sargon/Cargo.toml index 15d5c906a..1bd4fb341 100644 --- a/crates/sargon/Cargo.toml +++ b/crates/sargon/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sargon" # Don't forget to update version in crates/sargon-uniffi/Cargo.toml -version = "1.1.53" +version = "1.1.54" edition = "2021" build = "build.rs" diff --git a/crates/sargon/src/core/types/collections/identified_vec_of/identified_vec_of_modify.rs b/crates/sargon/src/core/types/collections/identified_vec_of/identified_vec_of_modify.rs index d07d9a84a..02166be54 100644 --- a/crates/sargon/src/core/types/collections/identified_vec_of/identified_vec_of_modify.rs +++ b/crates/sargon/src/core/types/collections/identified_vec_of/identified_vec_of_modify.rs @@ -99,6 +99,18 @@ impl IdentifiedVecOf { true } + /// Updates in place each item by `mutate`. + #[cfg(not(tarpaulin_include))] // false negative + #[inline] + pub fn update_all_with(&mut self, mut mutate: F) + where + F: FnMut(&mut V), + { + for item in self.0.values_mut() { + mutate(item) + } + } + /// Tries to mutate the value identified by `id`, if no such value exists /// an error is returned, the mutation is failable, if your return an `Err` /// in `mutate`, this method propagates that error. diff --git a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs index 15b050b76..ed1e9db95 100644 --- a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs +++ b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_dapp.rs @@ -40,6 +40,19 @@ pub struct AuthorizedDapp { pub preferences: AuthorizedDappPreferences, } +impl AuthorizedDapp { + /// Removes the referenced account for this dApp + pub(crate) fn remove_referenced_account( + &mut self, + account_address: &AccountAddress, + ) { + self.references_to_authorized_personas + .update_all_with(|persona| { + persona.remove_shared_account(account_address); + }); + } +} + impl IsNetworkAware for AuthorizedDapp { fn network_id(&self) -> NetworkID { self.network_id diff --git a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs index 61fe96345..9022fd4ef 100644 --- a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs +++ b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/authorized_persona_simple.rs @@ -36,6 +36,21 @@ pub struct AuthorizedPersonaSimple { pub shared_persona_data: SharedPersonaData, } +impl AuthorizedPersonaSimple { + /// Removes the referenced account from the shared accounts + pub(crate) fn remove_shared_account( + &mut self, + account_address: &AccountAddress, + ) -> bool { + if let Some(shared_accounts) = self.shared_accounts.as_mut() { + shared_accounts.remove_entry(&account_address); + true + } else { + false + } + } +} + impl AuthorizedPersonaSimple { pub fn description(&self) -> String { format!( diff --git a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs index 4561d73e1..2cf257754 100644 --- a/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs +++ b/crates/sargon/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs @@ -76,6 +76,10 @@ macro_rules! declare_shared_with_dapp { let ids_str = self.ids.iter().map(|v| v.to_string()).join(", "); format!("{} - shared ids: [{}]", self.request, ids_str) } + + pub fn remove_entry(&mut self, id: &<$id as Identifiable>::ID) -> Option<$id> { + self.ids.remove_id(id) + } } #[cfg(test)] diff --git a/crates/sargon/src/profile/v100/networks/network/authorized_dapps.rs b/crates/sargon/src/profile/v100/networks/network/authorized_dapps.rs index c73229ede..af1ae8dd1 100644 --- a/crates/sargon/src/profile/v100/networks/network/authorized_dapps.rs +++ b/crates/sargon/src/profile/v100/networks/network/authorized_dapps.rs @@ -5,6 +5,18 @@ decl_identified_vec_of!( AuthorizedDapp ); +impl AuthorizedDapps { + /// Remove referenced account from all the dApps + pub(crate) fn remove_referenced_account( + &mut self, + account_address: &AccountAddress, + ) { + self.update_all_with(|dapp| { + dapp.remove_referenced_account(account_address); + }) + } +} + impl HasSampleValues for AuthorizedDapps { /// A sample used to facilitate unit tests. fn sample() -> Self { diff --git a/crates/sargon/src/profile/v100/networks/network/profile_network.rs b/crates/sargon/src/profile/v100/networks/network/profile_network.rs index a4968810b..70184be8f 100644 --- a/crates/sargon/src/profile/v100/networks/network/profile_network.rs +++ b/crates/sargon/src/profile/v100/networks/network/profile_network.rs @@ -174,6 +174,32 @@ impl ProfileNetwork { None } } + + /// Hides the account associated with the `account_address` + pub(crate) fn hide_account( + &mut self, + account_address: &AccountAddress, + ) -> Option { + let account = self.update_account(account_address, |account| { + account.mark_as_hidden(); + }); + self.authorized_dapps + .remove_referenced_account(account_address); + account + } + + /// Tombstones the account associated with the `account_address` + pub(crate) fn tombstone_account( + &mut self, + account_address: &AccountAddress, + ) -> Option { + let account = self.update_account(account_address, |account| { + account.mark_as_tombstoned(); + }); + self.authorized_dapps + .remove_referenced_account(account_address); + account + } } impl HasSampleValues for ProfileNetwork { diff --git a/crates/sargon/src/profile/v100/networks/profile_networks.rs b/crates/sargon/src/profile/v100/networks/profile_networks.rs index 51e6a9ec8..6e58250a1 100644 --- a/crates/sargon/src/profile/v100/networks/profile_networks.rs +++ b/crates/sargon/src/profile/v100/networks/profile_networks.rs @@ -28,6 +28,36 @@ impl ProfileNetworks { }); self.get_account(address) } + + /// Hides the account associated with the `account_address` + pub(crate) fn hide_account( + &mut self, + account_address: &AccountAddress, + ) -> bool { + self.update_with(account_address.network_id(), |n| { + n.hide_account(account_address); + }) + } + + /// Tombstones the account associated with the `account_address` + pub(crate) fn tombstone_account( + &mut self, + account_address: &AccountAddress, + ) -> bool { + self.update_with(account_address.network_id(), |n| { + n.tombstone_account(account_address); + }) + } + + /// Tombstones the accounts + pub(crate) fn tombstone_accounts( + &mut self, + account_addresses: &Vec, + ) { + for account_address in account_addresses { + self.tombstone_account(&account_address); + } + } } impl ProfileNetworks { diff --git a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs index 517ed17cc..ef053d118 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_accounts.rs @@ -331,9 +331,11 @@ impl SargonOS { &self, account_address: AccountAddress, ) -> Result<()> { - let mut account = self.account_by_address(account_address)?; - account.mark_as_hidden(); - self.update_account(account).await + self.update_profile_with(|profile| { + profile.networks.hide_account(&account_address); + Ok(()) + }) + .await } /// Updates the profile by marking the account with `account_address` as tombstoned. @@ -341,9 +343,11 @@ impl SargonOS { &self, account_address: AccountAddress, ) -> Result<()> { - let mut account = self.account_by_address(account_address)?; - account.mark_as_tombstoned(); - self.update_account(account).await + self.update_profile_with(|profile| { + profile.networks.tombstone_account(&account_address); + Ok(()) + }) + .await } /// Updates the profile by marking the account with `account_addresses` as tombstoned. @@ -351,14 +355,11 @@ impl SargonOS { &self, account_addresses: Vec, ) -> Result<()> { - let mut accounts = account_addresses - .iter() - .map(|ad| self.account_by_address(ad.clone())) - .collect::>>()?; - - accounts.iter_mut().for_each(|a| a.mark_as_tombstoned()); - - self.update_accounts(Accounts::from(accounts)).await + self.update_profile_with(move |profile| { + profile.networks.tombstone_accounts(&account_addresses); + Ok(()) + }) + .await } } @@ -430,6 +431,7 @@ impl SargonOS { mod tests { use super::*; use actix_rt::time::timeout; + use futures::future::Shared; use std::{future::join, future::Future, time::Duration}; #[allow(clippy::upper_case_acronyms)] @@ -1009,44 +1011,343 @@ mod tests { async fn test_mark_account_as_hidden_becomes_hidden() { // ARRANGE let os = SUT::fast_boot().await; + let account = Account::sample_mainnet(); + + let authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); // ACT // so that we have at least one network (with one account) - let new_account = os - .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) - .await - .unwrap(); - os.mark_account_as_hidden(new_account.address) + os.with_timeout(|os| os.add_account(account.clone())) .await .unwrap(); + os.with_timeout(|os| { + os.update_profile_with(|profile| { + profile.networks.update_with(NetworkID::Mainnet, |network| { + network.authorized_dapps = authorized_dapps.clone(); + }); + Ok(()) + }) + }) + .await + .unwrap(); + + os.mark_account_as_hidden(account.address).await.unwrap(); + // ASSERT - assert!(os - .account_by_address(new_account.address) + assert!(os.account_by_address(account.address).unwrap().is_hidden()); + + let expected_authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); + + let updated_authorized_dapps = os + .profile() + .unwrap() + .clone() + .current_network() .unwrap() - .is_hidden()) + .authorized_dapps + .clone(); + pretty_assertions::assert_eq!( + updated_authorized_dapps, + expected_authorized_dapps + ) } #[actix_rt::test] async fn test_mark_account_as_tombstoned_becomes_tombstoned() { // ARRANGE let os = SUT::fast_boot().await; + let account = Account::sample_mainnet(); + + let authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); // ACT // so that we have at least one network (with one account) - let new_account = os - .with_timeout(|x| x.create_and_save_new_unnamed_mainnet_account()) + os.with_timeout(|os| os.add_account(account.clone())) .await .unwrap(); - os.mark_account_as_tombstoned(new_account.address) + + os.with_timeout(|os| { + os.update_profile_with(|profile| { + profile.networks.update_with(NetworkID::Mainnet, |network| { + network.authorized_dapps = authorized_dapps.clone(); + }); + Ok(()) + }) + }) + .await + .unwrap(); + + os.mark_account_as_tombstoned(account.address) .await .unwrap(); // ASSERT assert!(os - .account_by_address(new_account.address) + .account_by_address(account.address) + .unwrap() + .is_tombstoned()); + + let expected_authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 2 + }, + "ids": [ + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); + + let updated_authorized_dapps = os + .profile() + .unwrap() + .clone() + .current_network() .unwrap() - .is_tombstoned()) + .authorized_dapps + .clone(); + pretty_assertions::assert_eq!( + updated_authorized_dapps, + expected_authorized_dapps + ) } #[actix_rt::test] @@ -1054,48 +1355,194 @@ mod tests { // ARRANGE let os = SUT::fast_boot().await; + let account = Account::sample_mainnet(); + let other_account = Account::sample_mainnet_other(); + + let authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 3 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7", + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 3 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7", + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 3 + }, + "ids": [ + "account_rdx128dtethfy8ujrsfdztemyjk0kvhnah6dafr57frz85dcw2c8z0td87", + "account_rdx12y02nen8zjrq0k0nku98shjq7n05kvl3j9m5d3a6cpduqwzgmenjq7", + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); + // ACT - let new_accounts: Vec = os - .with_timeout(|x| { - join_all(vec![ - x.create_and_save_new_mainnet_account( - DisplayName::new("rip").unwrap(), - ), - x.create_and_save_new_mainnet_account( - DisplayName::new("rip").unwrap(), - ), - x.create_and_save_new_mainnet_account( - DisplayName::new("alive").unwrap(), - ), - ]) + os.with_timeout(|x| { + join_all(vec![ + x.add_account(account.clone()), + x.add_account(other_account.clone()), + x.add_account(Account::sample_mainnet_carol()), + ]) + }) + .await; + + os.with_timeout(|os| { + os.update_profile_with(|profile| { + profile.networks.update_with(NetworkID::Mainnet, |network| { + network.authorized_dapps = authorized_dapps.clone(); + }); + Ok(()) }) - .await - .into_iter() - .collect::, _>>() - .unwrap(); + }) + .await + .unwrap(); - let account_addresses_to_tombstone = new_accounts - .iter() - .filter(|a| a.display_name.value == "rip") - .map(|a| a.address) - .collect_vec(); os.with_timeout(|x| { - x.mark_accounts_as_tombstoned( - account_addresses_to_tombstone.clone(), - ) + x.mark_accounts_as_tombstoned(vec![ + account.address, + other_account.address, + ]) }) .await .unwrap(); // ASSERT - assert!(os - .accounts_on_current_network() + assert_eq!( + os.account_by_address(Account::sample_mainnet_carol().address) + .unwrap() + .is_tombstoned(), + false + ); + assert!(vec![account.address, other_account.address].iter().all( + |address| os + .account_by_address(address.clone()) + .unwrap() + .is_tombstoned() + )); + + let expected_authorized_dapps: AuthorizedDapps = serde_json::from_str(r#" + [ + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12x0xfz2yumu2qsh6yt0v8xjfc7et04vpsz775kc3yd3xvle4w5d5k5", + "displayName": "Radix Dashboard", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx122yy9pkfdrkam4evxcwh235c4qc52wujkwnt52q7vqxefhnlen489g", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "exactly", + "quantity": 3 + }, + "ids": [ + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + }, + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 3 + }, + "ids": [ + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + } + ] + }, + { + "networkID": 1, + "dAppDefinitionAddress": "account_rdx12xuhw6v30chdkhcu7qznz9vu926vxefr4h4tdvc0mdckg9rq4afx9t", + "displayName": "Gumball Club", + "referencesToAuthorizedPersonas": [ + { + "identityAddress": "identity_rdx12tw6rt9c4l56rz6p866e35tmzp556nymxmpj8hagfewq82kspctdyw", + "lastLogin": "2024-01-31T14:23:45.000Z", + "sharedAccounts": { + "request": { + "quantifier": "atLeast", + "quantity": 3 + }, + "ids": [ + "account_rdx129akrrsd9ctuphe99lesa8cf6auc5vqwdd2lu0ej6csncnuw9eedgv" + ] + }, + "sharedPersonaData": {} + } + ] + } + ] + "# + ).unwrap(); + + let updated_authorized_dapps = os + .profile() .unwrap() - .iter() - .all(|a| a.display_name.value != "rip")); - assert!(account_addresses_to_tombstone.iter().all(|address| os - .account_by_address(address.clone()) + .clone() + .current_network() .unwrap() - .is_tombstoned())) + .authorized_dapps + .clone(); + pretty_assertions::assert_eq!( + updated_authorized_dapps, + expected_authorized_dapps + ) } } diff --git a/crates/sargon/src/system/sargon_os/sargon_os_profile.rs b/crates/sargon/src/system/sargon_os/sargon_os_profile.rs index ce4bb9824..78ea831c5 100644 --- a/crates/sargon/src/system/sargon_os/sargon_os_profile.rs +++ b/crates/sargon/src/system/sargon_os/sargon_os_profile.rs @@ -51,16 +51,16 @@ impl SargonOS { }) .await?; } else { - self.profile_state_holder - .replace_profile_state_with(ProfileState::Loaded(profile))?; + self.profile_state_holder.replace_profile_state_with( + ProfileState::Loaded(profile.clone()), + )?; self.save_existing_profile().await?; - }; - let updated_profile = self.profile()?; - self.clients - .profile_state_change - .emit(ProfileState::Loaded(updated_profile)) - .await; + self.clients + .profile_state_change + .emit(ProfileState::Loaded(profile)) + .await; + }; Ok(()) } @@ -134,13 +134,19 @@ impl SargonOS { F: Fn(&mut Profile) -> Result, { let res = self.profile_state_holder.update_profile_with(mutate)?; - self.profile_state_holder.update_profile_with(|p| { + let profile = self.profile_state_holder.update_profile_with(|p| { p.update_header(None); - Ok(()) + Ok(p.clone()) })?; self.save_existing_profile() // tarpaulin will incorrectly flag next line is missed .await?; + + self.clients + .profile_state_change + .emit(ProfileState::Loaded(profile)) + .await; + Ok(res) }