diff --git a/crates/wasm/src/planner.rs b/crates/wasm/src/planner.rs index 3d9af7e8eb..33bb88f98b 100644 --- a/crates/wasm/src/planner.rs +++ b/crates/wasm/src/planner.rs @@ -27,6 +27,7 @@ use penumbra_governance::{ use penumbra_ibc::IbcRelay; use penumbra_keys::Address; use penumbra_num::Amount; +use penumbra_proto::core::keys::v1alpha1::AddressIndex; use penumbra_proto::view::v1alpha1::{NotesForVotingRequest, NotesRequest}; use penumbra_shielded_pool::{Ics20Withdrawal, Note, OutputPlan, SpendPlan}; use penumbra_stake::{rate::RateData, validator}; @@ -94,14 +95,17 @@ impl Planner { } /// Get all the note requests necessary to fulfill the current [`Balance`]. - pub fn notes_requests(&self) -> (Vec, Vec) { + pub fn notes_requests( + &self, + source: Option, + ) -> (Vec, Vec) { ( self.balance .required() .map(|Value { asset_id, amount }| NotesRequest { wallet_id: None, asset_id: Some(asset_id.into()), - address_index: None, + address_index: source.clone(), amount_to_spend: Some(amount.into()), include_spent: false, }) @@ -117,7 +121,7 @@ impl Planner { )| NotesForVotingRequest { wallet_id: None, votable_at_height: *start_block_height, - address_index: None, + address_index: source.clone(), }, ) .collect(), diff --git a/crates/wasm/src/storage.rs b/crates/wasm/src/storage.rs index d4b1baccd5..e37915873d 100644 --- a/crates/wasm/src/storage.rs +++ b/crates/wasm/src/storage.rs @@ -1,6 +1,7 @@ use indexed_db_futures::prelude::OpenDbRequest; use indexed_db_futures::{IdbDatabase, IdbQuerySource}; use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsValue; use web_sys::IdbTransactionMode::Readwrite; use penumbra_asset::asset::{DenomMetadata, Id}; @@ -10,7 +11,7 @@ use penumbra_proto::DomainType; use penumbra_sct::Nullifier; use penumbra_shielded_pool::{note, Note}; -use crate::error::WasmResult; +use crate::error::{WasmError, WasmResult}; use crate::note_record::SpendableNoteRecord; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -46,28 +47,39 @@ impl IndexedDBStorage { .transaction_on_one(&self.constants.tables.spendable_notes)?; let store = idb_tx.object_store(&self.constants.tables.spendable_notes)?; - let values = store.get_all()?.await?; - - let notes: Vec = values + let raw_values = store.get_all()?.await?; + let parsed_notes = raw_values .into_iter() - .map(|js_value| serde_wasm_bindgen::from_value(js_value).ok()) - .filter_map(|note_option| { - note_option.and_then(|note: SpendableNoteRecord| match request.asset_id.clone() { - Some(asset_id) => { - if note.note.asset_id() == asset_id.try_into().expect("Invalid asset id") - && note.height_spent.is_none() - { - Some(note) - } else { - None - } - } - None => Some(note), - }) - }) + .filter_map(|js_value| self.parse_note(js_value, &request).ok()) .collect(); - Ok(notes) + Ok(parsed_notes) + } + + fn parse_note( + &self, + js_value: JsValue, + request: &NotesRequest, + ) -> WasmResult { + let note: SpendableNoteRecord = serde_wasm_bindgen::from_value(js_value)?; + + let asset_id_matches = match &request.asset_id { + Some(asset_id) => note.note.asset_id() == asset_id.clone().try_into()?, + None => true, + }; + + let address_index_matches = match &request.address_index { + Some(address_index) => note.address_index.eq(&address_index.clone().try_into()?), + None => true, + }; + + if asset_id_matches && address_index_matches && note.height_spent.is_none() { + Ok(note) + } else { + Err(WasmError::Anyhow(anyhow::anyhow!( + "Note does not match the request" + ))) + } } pub async fn get_asset(&self, id: &Id) -> WasmResult> { diff --git a/crates/wasm/src/wasm_planner.rs b/crates/wasm/src/wasm_planner.rs index c875f56d14..2aebcfdd25 100644 --- a/crates/wasm/src/wasm_planner.rs +++ b/crates/wasm/src/wasm_planner.rs @@ -9,7 +9,7 @@ use penumbra_dex::swap_claim::SwapClaimPlan; use penumbra_proto::core::asset::v1alpha1::{DenomMetadata, Value}; use penumbra_proto::core::component::fee::v1alpha1::{Fee, GasPrices}; use penumbra_proto::core::component::ibc::v1alpha1::Ics20Withdrawal; -use penumbra_proto::core::keys::v1alpha1::Address; +use penumbra_proto::core::keys::v1alpha1::{Address, AddressIndex}; use penumbra_proto::core::transaction::v1alpha1::MemoPlaintext; use penumbra_proto::crypto::tct::v1alpha1::StateCommitment; use penumbra_proto::DomainType; @@ -184,11 +184,13 @@ impl WasmPlanner { } /// Builds transaction plan. - /// Refund address provided in the case there is extra balances to be returned. + /// Refund address provided in the case there is extra balances to be returned + // If source present, only spends funds from the given account. /// Arguments: /// refund_address: `Address` + /// source: `Option` /// Returns: `TransactionPlan` - pub async fn plan(&mut self, refund_address: JsValue) -> WasmResult { + pub async fn plan(&mut self, refund_address: JsValue, source: JsValue) -> WasmResult { utils::set_panic_hook(); // Calculate the gas that needs to be paid for the transaction based on the configured gas prices. @@ -199,7 +201,9 @@ impl WasmPlanner { let mut spendable_notes = Vec::new(); - let (spendable_requests, _) = self.planner.notes_requests(); + let source_address_index: Option = serde_wasm_bindgen::from_value(source)?; + + let (spendable_requests, _) = self.planner.notes_requests(source_address_index); for request in spendable_requests { let notes = self.storage.get_notes(request); spendable_notes.extend(notes.await?);