miden_client/transactions/request.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
//! Contains structures and functions related to transaction creation.
use alloc::{
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec::Vec,
};
use core::fmt;
use miden_lib::notes::{create_p2id_note, create_p2idr_note, create_swap_note};
use miden_objects::{
accounts::AccountId,
assembly::AssemblyError,
assets::{Asset, FungibleAsset},
crypto::{
merkle::{InnerNodeInfo, MerkleStore},
rand::FeltRng,
},
notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType, PartialNote},
transaction::{OutputNote, TransactionArgs, TransactionScript},
vm::AdviceMap,
Digest, Felt, FieldElement, NoteError, Word,
};
use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
use super::{
script_builder::{AccountCapabilities, TransactionScriptBuilder},
TransactionScriptBuilderError,
};
// TRANSACTION REQUEST
// ================================================================================================
pub type NoteArgs = Word;
/// Specifies a transaction script to be executed in a transaction.
///
/// A transaction script is a program which is executed after scripts of all input notes have been
/// executed.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TransactionScriptTemplate {
/// Specifies the exact transaction script to be executed in a transaction.
CustomScript(TransactionScript),
/// Specifies that the transaction script must create the specified output notes.
///
/// It is up to the client to determine how the output notes will be created and this will
/// depend on the capabilities of the account the transaction request will be applied to.
/// For example, for Basic Wallets, this may involve invoking `create_note` procedure.
SendNotes(Vec<PartialNote>),
}
/// A request for a transaction that can be executed by an account.
///
/// A request contains information about input notes to be consumed by the transaction (if any),
/// description of the transaction script to be executed (if any), and a set of notes expected
/// to be generated by the transaction or by consuming notes generated by the transaction.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TransactionRequest {
// Notes to be consumed by the transaction that are not authenticated.
unauthenticated_input_notes: Vec<Note>,
/// Notes to be consumed by the transaction together with their (optional) arguments. This
/// has to include both authenticated and unauthenticated notes.
input_notes: BTreeMap<NoteId, Option<NoteArgs>>,
/// Template for the creation of the transaction script.
script_template: Option<TransactionScriptTemplate>,
/// A map of notes expected to be generated by the transactions.
expected_output_notes: BTreeMap<NoteId, Note>,
/// A map of details and tags of notes we expect to be created as part of future transactions
/// with their respective tags.
///
/// For example, after a swap note is consumed, a payback note is expected to be created.
expected_future_notes: BTreeMap<NoteId, (NoteDetails, NoteTag)>,
/// Initial state of the `AdviceMap` that provides data during runtime.
advice_map: AdviceMap,
/// Initial state of the `MerkleStore` that provides data during runtime.
merkle_store: MerkleStore,
/// Foreign account data requirements. At execution time, account state will be retrieved from
/// the network, and injected as advice inputs. Additionally, the account's code will be
/// added to the executor and prover.
foreign_account_ids: BTreeSet<AccountId>,
/// The number of blocks in relation to the transaction's reference block after which the
/// transaction will expire.
expiration_delta: Option<u16>,
}
impl TransactionRequest {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------
/// Creates a new, empty [TransactionRequest].
pub fn new() -> Self {
Self {
unauthenticated_input_notes: vec![],
input_notes: BTreeMap::new(),
script_template: None,
expected_output_notes: BTreeMap::new(),
expected_future_notes: BTreeMap::new(),
advice_map: AdviceMap::default(),
merkle_store: MerkleStore::default(),
expiration_delta: None,
foreign_account_ids: BTreeSet::default(),
}
}
/// Adds the specified notes as unauthenticated input notes to the transaction request.
pub fn with_unauthenticated_input_notes(
mut self,
notes: impl IntoIterator<Item = (Note, Option<NoteArgs>)>,
) -> Self {
for (note, argument) in notes {
self.input_notes.insert(note.id(), argument);
self.unauthenticated_input_notes.push(note);
}
self
}
/// Adds the specified notes as authenticated input notes to the transaction request.
pub fn with_authenticated_input_notes(
mut self,
notes: impl IntoIterator<Item = (NoteId, Option<NoteArgs>)>,
) -> Self {
for (note_id, argument) in notes {
self.input_notes.insert(note_id, argument);
}
self
}
/// Specifies the output notes that should be created in the transaction script and will
/// be used as a transaction script template. These notes will also be added the the expected
/// output notes of the transaction.
///
/// If a transaction script template is already set (e.g. by calling `with_custom_script`), this
/// method will return an error.
pub fn with_own_output_notes(
mut self,
notes: impl IntoIterator<Item = OutputNote>,
) -> Result<Self, TransactionRequestError> {
if self.script_template.is_some() {
return Err(TransactionRequestError::ScriptTemplateError(
"Cannot set own notes when a script template is already set".to_string(),
));
}
let mut own_notes = Vec::new();
for note in notes {
match note {
OutputNote::Full(note) => {
self.expected_output_notes.insert(note.id(), note.clone());
own_notes.push(note.into());
},
OutputNote::Partial(note) => own_notes.push(note),
OutputNote::Header(_) => return Err(TransactionRequestError::InvalidNoteVariant),
}
}
self.script_template = Some(TransactionScriptTemplate::SendNotes(own_notes));
Ok(self)
}
/// Specifies a custom transaction script to be used.
///
/// If a script template is already set (e.g. by calling `with_own_output_notes`), this method
/// will return an error.
pub fn with_custom_script(
mut self,
script: TransactionScript,
) -> Result<Self, TransactionRequestError> {
if self.script_template.is_some() {
return Err(TransactionRequestError::ScriptTemplateError(
"Cannot set custom script when a script template is already set".to_string(),
));
} else if self.expiration_delta.is_some() {
return Err(TransactionRequestError::ScriptTemplateError(
"Cannot set custom script when an expiration delta is already set".to_string(),
));
}
self.script_template = Some(TransactionScriptTemplate::CustomScript(script));
Ok(self)
}
/// Specifies public account IDs that contain data that the transaction will utilize.
///
/// At execution, the client queries the node and retrieves the state and current code for
/// these accounts, and injects them as advice inputs.
///
/// # Errors
///
/// - If `foreign_account_ids` contains an ID corresponding to a private account.
pub fn with_public_foreign_accounts(
mut self,
foreign_account_ids: impl IntoIterator<Item = AccountId>,
) -> Result<Self, TransactionRequestError> {
for account_id in foreign_account_ids {
if !account_id.is_public() {
return Err(TransactionRequestError::InvalidForeignAccountId(account_id));
}
self.foreign_account_ids.insert(account_id);
}
Ok(self)
}
/// Specifies a transaction's expected output notes.
///
/// The set of specified notes is treated as a subset of the notes that may be created by a
/// transaction. That is, the transaction must create all the specified expected notes, but it
/// may also create other notes which are not included in the set of expected notes.
pub fn with_expected_output_notes(mut self, notes: Vec<Note>) -> Self {
self.expected_output_notes =
BTreeMap::from_iter(notes.into_iter().map(|note| (note.id(), note)));
self
}
/// Specifies a set of notes which may be created when a transaction's output notes are
/// consumed.
///
/// For example, after a SWAP note is consumed, a payback note is expected to be created. This
/// allows the client to track this note accordingly.
pub fn with_expected_future_notes(mut self, notes: Vec<(NoteDetails, NoteTag)>) -> Self {
self.expected_future_notes =
BTreeMap::from_iter(notes.into_iter().map(|note| (note.0.id(), note)));
self
}
/// Extends the advice map with the specified ([Word], Vec<[Felt]>) pairs.
pub fn extend_advice_map<T: IntoIterator<Item = (Digest, Vec<Felt>)>>(
mut self,
iter: T,
) -> Self {
self.advice_map.extend(iter);
self
}
/// Extends the merkle store with the specified [InnerNodeInfo] elements.
pub fn extend_merkle_store<T: IntoIterator<Item = InnerNodeInfo>>(mut self, iter: T) -> Self {
self.merkle_store.extend(iter);
self
}
/// The number of blocks in relation to the transaction's reference block after which the
/// transaction will expire.
///
/// Setting transaction expiration delta defines an upper bound for transaction expiration,
/// but other code executed during the transaction may impose an even smaller transaction
/// expiration delta.
pub fn with_expiration_delta(
mut self,
expiration_delta: u16,
) -> Result<Self, TransactionRequestError> {
if let Some(TransactionScriptTemplate::CustomScript(_)) = self.script_template {
return Err(TransactionRequestError::ScriptTemplateError(
"Cannot set expiration delta when a custom script is set".to_string(),
));
}
self.expiration_delta = Some(expiration_delta);
Ok(self)
}
// STANDARDIZED REQUESTS
// --------------------------------------------------------------------------------------------
/// Returns a new [TransactionRequest] for a transaction to consume the specified notes.
///
/// - `note_ids` is a list of note IDs to be consumed.
pub fn consume_notes(note_ids: Vec<NoteId>) -> Self {
let input_notes = note_ids.into_iter().map(|id| (id, None));
Self::new().with_authenticated_input_notes(input_notes)
}
/// Returns a new [TransactionRequest] for a transaction to mint fungible assets. This request
/// must be executed against a fungible faucet account.
///
/// - `asset` is the fungible asset to be minted.
/// - `target_id` is the account ID of the account to receive the minted asset.
/// - `note_type` determines the visibility of the note to be created.
/// - `rng` is the random number generator used to generate the serial number for the created
/// note.
pub fn mint_fungible_asset(
asset: FungibleAsset,
target_id: AccountId,
note_type: NoteType,
rng: &mut impl FeltRng,
) -> Result<Self, TransactionRequestError> {
let created_note = create_p2id_note(
asset.faucet_id(),
target_id,
vec![asset.into()],
note_type,
Felt::ZERO,
rng,
)?;
TransactionRequest::new().with_own_output_notes(vec![OutputNote::Full(created_note)])
}
/// Returns a new [TransactionRequest] for a transaction to send a P2ID or P2IDR note. This
/// request must be executed against the wallet sender account.
///
/// - `payment_data` is the data for the payment transaction that contains the asset to be
/// transferred, the sender account ID, and the target account ID.
/// - `recall_height` is the block height after which the sender can recall the assets. If None,
/// a P2ID note is created. If Some(), a P2IDR note is created.
/// - `note_type` determines the visibility of the note to be created.
/// - `rng` is the random number generator used to generate the serial number for the created
/// note.
pub fn pay_to_id(
payment_data: PaymentTransactionData,
recall_height: Option<u32>,
note_type: NoteType,
rng: &mut impl FeltRng,
) -> Result<Self, TransactionRequestError> {
let PaymentTransactionData {
assets,
sender_account_id,
target_account_id,
} = payment_data;
let created_note = if let Some(recall_height) = recall_height {
create_p2idr_note(
sender_account_id,
target_account_id,
assets,
note_type,
Felt::ZERO,
recall_height,
rng,
)?
} else {
create_p2id_note(
sender_account_id,
target_account_id,
assets,
note_type,
Felt::ZERO,
rng,
)?
};
TransactionRequest::new().with_own_output_notes(vec![OutputNote::Full(created_note)])
}
/// Returns a new [TransactionRequest] for a transaction to send a SWAP note. This request must
/// be executed against the wallet sender account.
///
/// - `swap_data` is the data for the swap transaction that contains the sender account ID, the
/// offered asset, and the requested asset.
/// - `note_type` determines the visibility of the note to be created.
/// - `rng` is the random number generator used to generate the serial number for the created
/// note.
pub fn swap(
swap_data: SwapTransactionData,
note_type: NoteType,
rng: &mut impl FeltRng,
) -> Result<Self, TransactionRequestError> {
// The created note is the one that we need as the output of the tx, the other one is the
// one that we expect to receive and consume eventually.
let (created_note, payback_note_details) = create_swap_note(
swap_data.account_id(),
swap_data.offered_asset(),
swap_data.requested_asset(),
note_type,
Felt::ZERO,
rng,
)?;
let payback_tag =
NoteTag::from_account_id(swap_data.account_id(), NoteExecutionMode::Local)?;
TransactionRequest::new()
.with_expected_future_notes(vec![(payback_note_details, payback_tag)])
.with_own_output_notes(vec![OutputNote::Full(created_note)])
}
// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
/// Returns a reference to the transaction request's unauthenticated note list.
pub fn unauthenticated_input_notes(&self) -> &[Note] {
&self.unauthenticated_input_notes
}
/// Returns an iterator over unauthenticated note IDs for the transaction request.
pub fn unauthenticated_input_note_ids(&self) -> impl Iterator<Item = NoteId> + '_ {
self.unauthenticated_input_notes.iter().map(|note| note.id())
}
/// Returns an iterator over authenticated input note IDs for the transaction request.
pub fn authenticated_input_note_ids(&self) -> impl Iterator<Item = NoteId> + '_ {
let unauthenticated_note_ids: BTreeSet<NoteId> =
BTreeSet::from_iter(self.unauthenticated_input_note_ids());
self.input_notes()
.iter()
.map(|(note_id, _)| *note_id)
.filter(move |note_id| !unauthenticated_note_ids.contains(note_id))
}
/// Returns a mapping for input note IDs and their optional [NoteArgs].
pub fn input_notes(&self) -> &BTreeMap<NoteId, Option<NoteArgs>> {
&self.input_notes
}
/// Returns a list of all input note IDs.
pub fn get_input_note_ids(&self) -> Vec<NoteId> {
self.input_notes.keys().cloned().collect()
}
/// Returns a map of note IDs to their respective [NoteArgs]. The result will include
/// exclusively note IDs for notes for which [NoteArgs] have been defined.
pub fn get_note_args(&self) -> BTreeMap<NoteId, NoteArgs> {
self.input_notes
.iter()
.filter_map(|(note, args)| args.map(|a| (*note, a)))
.collect()
}
/// Returns an iterator over the expected output notes.
pub fn expected_output_notes(&self) -> impl Iterator<Item = &Note> {
self.expected_output_notes.values()
}
/// Returns an iterator over expected future notes.
pub fn expected_future_notes(&self) -> impl Iterator<Item = &(NoteDetails, NoteTag)> {
self.expected_future_notes.values()
}
/// Returns the [TransactionScriptTemplate].
pub fn script_template(&self) -> &Option<TransactionScriptTemplate> {
&self.script_template
}
/// Returns the [AdviceMap] for the transaction request.
pub fn advice_map(&self) -> &AdviceMap {
&self.advice_map
}
/// Returns the [MerkleStore] for the transaction request.
pub fn merkle_store(&self) -> &MerkleStore {
&self.merkle_store
}
/// Returns the IDs of the required foreign accounts for the transaction request.
pub fn foreign_accounts(&self) -> &BTreeSet<AccountId> {
&self.foreign_account_ids
}
/// Converts the [TransactionRequest] into [TransactionArgs] in order to be executed by a Miden
/// host.
pub(super) fn into_transaction_args(self, tx_script: TransactionScript) -> TransactionArgs {
let note_args = self.get_note_args();
let TransactionRequest {
expected_output_notes,
advice_map,
merkle_store,
..
} = self;
let mut tx_args = TransactionArgs::new(Some(tx_script), note_args.into(), advice_map);
tx_args.extend_expected_output_notes(expected_output_notes.into_values());
tx_args.extend_merkle_store(merkle_store.inner_nodes());
tx_args
}
pub(crate) fn build_transaction_script(
&self,
account_capabilities: AccountCapabilities,
) -> Result<TransactionScript, TransactionRequestError> {
match &self.script_template {
Some(TransactionScriptTemplate::CustomScript(script)) => Ok(script.clone()),
Some(TransactionScriptTemplate::SendNotes(notes)) => {
let tx_script_builder =
TransactionScriptBuilder::new(account_capabilities, self.expiration_delta);
Ok(tx_script_builder.build_send_notes_script(notes)?)
},
None => {
if self.input_notes.is_empty() {
Err(TransactionRequestError::NoInputNotes)
} else {
let tx_script_builder =
TransactionScriptBuilder::new(account_capabilities, self.expiration_delta);
Ok(tx_script_builder.build_auth_script()?)
}
},
}
}
}
impl Serializable for TransactionRequest {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.unauthenticated_input_notes.write_into(target);
self.input_notes.write_into(target);
match &self.script_template {
None => target.write_u8(0),
Some(TransactionScriptTemplate::CustomScript(script)) => {
target.write_u8(1);
script.write_into(target);
},
Some(TransactionScriptTemplate::SendNotes(notes)) => {
target.write_u8(2);
notes.write_into(target);
},
}
self.expected_output_notes.write_into(target);
self.expected_future_notes.write_into(target);
self.advice_map.clone().into_iter().collect::<Vec<_>>().write_into(target);
self.merkle_store.write_into(target);
self.foreign_account_ids.write_into(target);
self.expiration_delta.write_into(target);
}
}
impl Deserializable for TransactionRequest {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let unauthenticated_input_notes = Vec::<Note>::read_from(source)?;
let input_notes = BTreeMap::<NoteId, Option<NoteArgs>>::read_from(source)?;
let script_template = match source.read_u8()? {
0 => None,
1 => {
let transaction_script = TransactionScript::read_from(source)?;
Some(TransactionScriptTemplate::CustomScript(transaction_script))
},
2 => {
let notes = Vec::<PartialNote>::read_from(source)?;
Some(TransactionScriptTemplate::SendNotes(notes))
},
_ => {
return Err(DeserializationError::InvalidValue(
"Invalid script template type".to_string(),
))
},
};
let expected_output_notes = BTreeMap::<NoteId, Note>::read_from(source)?;
let expected_future_notes = BTreeMap::<NoteId, (NoteDetails, NoteTag)>::read_from(source)?;
let mut advice_map = AdviceMap::new();
let advice_vec = Vec::<(Digest, Vec<Felt>)>::read_from(source)?;
advice_map.extend(advice_vec);
let merkle_store = MerkleStore::read_from(source)?;
let foreign_account_ids = BTreeSet::<AccountId>::read_from(source)?;
let expiration_delta = Option::<u16>::read_from(source)?;
Ok(TransactionRequest {
unauthenticated_input_notes,
input_notes,
script_template,
expected_output_notes,
expected_future_notes,
advice_map,
merkle_store,
foreign_account_ids,
expiration_delta,
})
}
}
impl Default for TransactionRequest {
fn default() -> Self {
Self::new()
}
}
// TRANSACTION REQUEST ERROR
// ================================================================================================
/// Errors related to a [TransactionRequest]
#[derive(Debug)]
pub enum TransactionRequestError {
InvalidForeignAccountId(AccountId),
InputNoteNotAuthenticated,
InputNotesMapMissingUnauthenticatedNotes,
InvalidNoteVariant,
InvalidSenderAccount(AccountId),
InvalidTransactionScript(AssemblyError),
NoInputNotes,
ScriptTemplateError(String),
NoteNotFound(String),
NoteCreationError(NoteError),
TransactionScriptBuilderError(TransactionScriptBuilderError),
}
impl From<TransactionScriptBuilderError> for TransactionRequestError {
fn from(err: TransactionScriptBuilderError) -> Self {
Self::TransactionScriptBuilderError(err)
}
}
impl fmt::Display for TransactionRequestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidForeignAccountId(acc_id) => write!(f, "Requested foreign account with ID {acc_id} is not public"),
Self::InputNoteNotAuthenticated => write!(f, "Every authenticated note to be consumed should be committed and contain a valid inclusion proof"),
Self::InputNotesMapMissingUnauthenticatedNotes => write!(f, "The input notes map should include keys for all provided unauthenticated input notes"),
Self::InvalidNoteVariant => write!(f, "Own notes should be either full or partial, but not header"),
Self::InvalidSenderAccount(account_id) => write!(f, "Invalid sender account ID: {}", account_id),
Self::InvalidTransactionScript(err) => write!(f, "Invalid transaction script: {}", err),
Self::NoInputNotes => write!(f, "A transaction without output notes must have at least one input note"),
Self::ScriptTemplateError(err) => write!(f, "Transaction script template error: {}", err),
Self::NoteNotFound(err) => write!(f, "Note not found: {}", err),
Self::NoteCreationError(err) => write!(f, "Note creation error: {}", err),
Self::TransactionScriptBuilderError(err) => write!(f, "Transaction script builder error: {}", err),
}
}
}
impl From<NoteError> for TransactionRequestError {
fn from(err: NoteError) -> Self {
Self::NoteCreationError(err)
}
}
#[cfg(feature = "std")]
impl std::error::Error for TransactionRequestError {}
// PAYMENT TRANSACTION DATA
// ================================================================================================
/// Contains information about a payment transaction.
#[derive(Clone, Debug)]
pub struct PaymentTransactionData {
/// Assets that are meant to be sent to the target account.
assets: Vec<Asset>,
/// Account ID of the sender account.
sender_account_id: AccountId,
/// Account ID of the receiver account.
target_account_id: AccountId,
}
impl PaymentTransactionData {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------
/// Creates a new [PaymentTransactionData]
pub fn new(
assets: Vec<Asset>,
sender_account_id: AccountId,
target_account_id: AccountId,
) -> PaymentTransactionData {
PaymentTransactionData {
assets,
sender_account_id,
target_account_id,
}
}
/// Returns the executor [AccountId]
pub fn account_id(&self) -> AccountId {
self.sender_account_id
}
/// Returns the target [AccountId]
pub fn target_account_id(&self) -> AccountId {
self.target_account_id
}
/// Returns the transaction's list of [Asset]
pub fn assets(&self) -> &Vec<Asset> {
&self.assets
}
}
// SWAP TRANSACTION DATA
// ================================================================================================
/// Contains information related to a swap transaction.
///
/// A swap transaction involves creating a SWAP note, which will carry the offered asset and which,
/// when consumed, will create a payback note that carries the requested asset taken from the
/// consumer account's vault.
#[derive(Clone, Debug)]
pub struct SwapTransactionData {
/// Account ID of the sender account.
sender_account_id: AccountId,
/// Asset that is offered in the swap.
offered_asset: Asset,
/// Asset that is expected in the payback note generated as a result of the swap.
requested_asset: Asset,
}
impl SwapTransactionData {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------
/// Creates a new [SwapTransactionData]
pub fn new(
sender_account_id: AccountId,
offered_asset: Asset,
requested_asset: Asset,
) -> SwapTransactionData {
SwapTransactionData {
sender_account_id,
offered_asset,
requested_asset,
}
}
/// Returns the executor [AccountId]
pub fn account_id(&self) -> AccountId {
self.sender_account_id
}
/// Returns the transaction offered [Asset]
pub fn offered_asset(&self) -> Asset {
self.offered_asset
}
/// Returns the transaction requested [Asset]
pub fn requested_asset(&self) -> Asset {
self.requested_asset
}
}
// TESTS
// ================================================================================================
#[cfg(test)]
mod tests {
use std::vec::Vec;
use miden_lib::notes::create_p2id_note;
use miden_objects::{
accounts::{AccountId, AccountType},
assets::FungibleAsset,
crypto::rand::{FeltRng, RpoRandomCoin},
notes::{NoteExecutionMode, NoteTag, NoteType},
transaction::OutputNote,
Digest, Felt, ZERO,
};
use miden_tx::utils::{Deserializable, Serializable};
use super::TransactionRequest;
#[test]
fn transaction_request_serialization() {
let sender_id = AccountId::new_dummy([0u8; 32], AccountType::RegularAccountImmutableCode);
let target_id = AccountId::new_dummy([1u8; 32], AccountType::RegularAccountImmutableCode);
let faucet_id = AccountId::new_dummy([2u8; 32], AccountType::FungibleFaucet);
let mut rng = RpoRandomCoin::new(Default::default());
let mut notes = vec![];
for i in 0..6 {
let note = create_p2id_note(
sender_id,
target_id,
vec![FungibleAsset::new(faucet_id, 100 + i).unwrap().into()],
NoteType::Private,
ZERO,
&mut rng,
)
.unwrap();
notes.push(note);
}
let mut advice_vec: Vec<(Digest, Vec<Felt>)> = vec![];
for i in 0..10 {
advice_vec.push((Digest::new(rng.draw_word()), vec![Felt::new(i)]));
}
// This transaction request wouldn't be valid in a real scenario, it's intended for testing
let tx_request = TransactionRequest::new()
.with_authenticated_input_notes(vec![(notes.pop().unwrap().id(), None)])
.with_unauthenticated_input_notes(vec![(notes.pop().unwrap(), None)])
.with_expected_output_notes(vec![notes.pop().unwrap()])
.with_expected_future_notes(vec![(
notes.pop().unwrap().into(),
NoteTag::from_account_id(sender_id, NoteExecutionMode::Local).unwrap(),
)])
.extend_advice_map(advice_vec)
.with_public_foreign_accounts([target_id])
.unwrap()
.with_own_output_notes(vec![
OutputNote::Full(notes.pop().unwrap()),
OutputNote::Partial(notes.pop().unwrap().into()),
])
.unwrap();
let mut buffer = Vec::new();
tx_request.write_into(&mut buffer);
let deserialized_tx_request = TransactionRequest::read_from_bytes(&buffer).unwrap();
assert_eq!(tx_request, deserialized_tx_request);
}
}