-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Recursive fn calls to spend more notes. (#1779)
Closes #1422 Also fixed a bug where we allowed any sender to spend notes.
- Loading branch information
1 parent
f5546b3
commit aad25c6
Showing
2 changed files
with
92 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,184 +1,132 @@ | ||
use dep::std::option::Option; | ||
use dep::aztec::context::PrivateContext; | ||
// docs:start:encrypted_import | ||
|
||
use dep::aztec::log::emit_encrypted_log; | ||
|
||
// docs:end:encrypted_import | ||
use dep::aztec::note::note_getter_options::NoteGetterOptions; | ||
use dep::aztec::note::note_getter_options::{NoteGetterOptions, SortOrder}; | ||
use dep::aztec::oracle::get_public_key::get_public_key; | ||
use dep::aztec::state_vars::set::Set; | ||
use dep::aztec::types::point::Point; | ||
use crate::{ | ||
filter::filter_notes_min_sum, | ||
value_note::{ValueNote, VALUE_NOTE_LEN}, | ||
}; | ||
|
||
fn spend_notes( | ||
// Sort the note values (0th field) in descending order. | ||
// Pick the fewest notes whose sum is equal to or greater than `amount`. | ||
fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions<ValueNote, VALUE_NOTE_LEN, Field> { | ||
NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(0, SortOrder.DESC) | ||
} | ||
|
||
// Creates a new note for the recipient. | ||
// Inserts it to the recipient's set of notes. | ||
fn increment( | ||
context: &mut PrivateContext, | ||
balance: Set<ValueNote, VALUE_NOTE_LEN>, | ||
amount: Field, | ||
owner: Field, | ||
recipient: Field, | ||
) { | ||
let options = NoteGetterOptions::with_filter(filter_notes_min_sum, amount); | ||
let opt_notes = balance.get_notes(context, options); | ||
|
||
let mut sum = 0; | ||
for i in 0..opt_notes.len() { | ||
if opt_notes[i].is_some() { | ||
let note = opt_notes[i].unwrap_unchecked(); | ||
|
||
// Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while | ||
// spending someone else's notes). | ||
assert(note.owner == owner); | ||
|
||
// Removes the note from the owner's set of notes. | ||
balance.remove(context, note); | ||
|
||
sum += note.value; | ||
} | ||
} | ||
|
||
assert(sum as u120 >= amount as u120); | ||
|
||
// Creates change note for the owner. | ||
let change_value = sum - amount; | ||
let mut change_note = ValueNote::new(change_value, owner); | ||
balance.insert(context, &mut change_note); | ||
|
||
// Emit the newly created encrypted note preimages via oracle calls. | ||
let mut encrypted_data = [0; VALUE_NOTE_LEN]; | ||
if change_value != 0 { | ||
encrypted_data = change_note.serialise(); | ||
}; | ||
let mut note = ValueNote::new(amount, recipient); | ||
create_note(context, balance, recipient, &mut note); | ||
|
||
let encryption_pub_key = get_public_key(owner); | ||
emit_encrypted_log( | ||
context, | ||
(*context).this_address(), | ||
balance.storage_slot, | ||
encryption_pub_key, | ||
encrypted_data, | ||
); | ||
// It won't compile if Set.insert() is in an if statement :( | ||
// if amount as u120 > 0 { | ||
// create_note(context, balance, recipient, &mut note); | ||
// } | ||
} | ||
|
||
/* | ||
Spends one note from a set of 4 notes. | ||
Details: Reads 4 notes from the user's `balance`: [n_{o}, n_{o+1}, n_{o+2}, n_{o+3}] | ||
where "o" is the note_offset. Then, spends the note: [n_{o + i}] | ||
where "i" is the spend_note_index. This gives more control to the user on which | ||
note is to be spent. | ||
*/ | ||
fn spend_one_note( | ||
// Find some of the `owner`'s notes whose values add up to the `amount`. | ||
// Remove those notes. | ||
// If the value of the removed notes exceeds the requested `amount`, create a new note containing the excess value, so that exactly `amount` is removed. | ||
// Fail if the sum of the selected notes is less than the amount. | ||
fn decrement( | ||
context: &mut PrivateContext, | ||
balance: Set<ValueNote, VALUE_NOTE_LEN>, | ||
amount: Field, | ||
owner: Field, | ||
spend_note_offset: u32, | ||
) { | ||
let options = NoteGetterOptions::new().set_limit(1).set_offset(spend_note_offset); | ||
let opt_notes = balance.get_notes(context, options); | ||
|
||
// The note should always exist. | ||
let note = opt_notes[0].unwrap(); | ||
|
||
// Ensure the notes are actually owned by the owner (to prevent user from generating a valid proof while | ||
// spending someone else's notes). | ||
assert(owner == note.owner); | ||
|
||
// Removes the note from the owner's set of notes. | ||
balance.remove(context, note); | ||
|
||
let note_value = note.value; | ||
|
||
// Assert that the note chosen to spend has enough funds. | ||
assert(note_value as u64 >= amount as u64); | ||
|
||
// Creates change note for the owner. | ||
let change_value = note_value - amount; | ||
let mut change_note = ValueNote::new(change_value, owner); | ||
|
||
// Insert the change note to the owner's sets of notes. | ||
balance.insert(context, &mut change_note); | ||
|
||
// Emit the newly created encrypted note preimages via oracle calls. | ||
let mut encrypted_data = [0; VALUE_NOTE_LEN]; | ||
if change_value != 0 { | ||
encrypted_data = change_note.serialise(); | ||
}; | ||
|
||
let encryption_pub_key = get_public_key(owner); | ||
emit_encrypted_log( | ||
context, | ||
context.inputs.call_context.storage_contract_address, | ||
balance.storage_slot, | ||
encryption_pub_key, | ||
encrypted_data, | ||
); | ||
let sum = decrement_by_at_most(context, balance, amount, owner); | ||
assert(sum == amount); | ||
} | ||
|
||
fn send_note( | ||
// Similar to `decrement`, except that it doesn't fail if the decremented amount is less than max_amount. | ||
// The motivation behind this function is that there is an upper-bound on the number of notes a function may | ||
// read and nullify. The requested decrementation `amount` might be spread across too many of the `owner`'s | ||
// notes to 'fit' within this upper-bound, so we might have to remove an amount less than `amount`. A common | ||
// pattern is to repeatedly call this function across many function calls, until enough notes have been nullified to | ||
// equal `amount`. | ||
// | ||
// It returns the decremented amount, which should be less than or equal to max_amount. | ||
fn decrement_by_at_most( | ||
context: &mut PrivateContext, | ||
balance: Set<ValueNote, VALUE_NOTE_LEN>, | ||
amount: Field, | ||
recipient: Field, | ||
) { | ||
// Creates new note for the recipient. | ||
let mut note = ValueNote::new(amount, recipient); | ||
|
||
// Insert the new note to the recipient's set of notes. | ||
balance.insert(context, &mut note); | ||
|
||
// Emit the newly created encrypted note preimages via oracle calls. | ||
// docs:start:encrypted | ||
max_amount: Field, | ||
owner: Field, | ||
) -> Field { | ||
let options = create_note_getter_options_for_decreasing_balance(max_amount); | ||
let opt_notes = balance.get_notes(context, options); | ||
|
||
let application_contract_address = (*context).this_address(); | ||
let note_storage_slot = balance.storage_slot; | ||
let encryption_pub_key = get_public_key(recipient); | ||
let encrypted_data = note.serialise(); | ||
let mut decremented = 0; | ||
for i in 0..opt_notes.len() { | ||
if opt_notes[i].is_some() { | ||
decremented += destroy_note(context, balance, owner, opt_notes[i].unwrap_unchecked()); | ||
} | ||
} | ||
|
||
emit_encrypted_log( | ||
context, | ||
application_contract_address, | ||
note_storage_slot, | ||
encryption_pub_key, | ||
encrypted_data, | ||
); | ||
// Add the change value back to the owner's balance. | ||
let mut change_value = 0; | ||
if decremented as u120 > max_amount as u120 { | ||
change_value = decremented - max_amount; | ||
decremented -= change_value; | ||
} | ||
increment(context, balance, change_value, owner); | ||
|
||
// docs:end:encrypted | ||
decremented | ||
} | ||
|
||
/* | ||
Sends three amounts to three recipients. | ||
Why three? Because one private call currently allows `MAX_NEW_COMMITMENTS_PER_CALL = 4` output commitments. | ||
So we split the output notes as: 3 to recipients + 1 to the owner (the change note). | ||
*/ | ||
fn send_notes<NUM_RECIPIENTS>( | ||
fn create_note( | ||
context: &mut PrivateContext, | ||
recipient_balances: [Set<ValueNote, VALUE_NOTE_LEN>; NUM_RECIPIENTS], | ||
amounts: [Field; NUM_RECIPIENTS], | ||
recipients: [Field; NUM_RECIPIENTS], | ||
balance: Set<ValueNote, VALUE_NOTE_LEN>, | ||
owner: Field, | ||
note: &mut ValueNote, | ||
) { | ||
for i in 0..recipients.len() { | ||
// Creates a new note for the i-th recipients | ||
let mut recipient_note = ValueNote::new(amounts[i], recipients[i]); | ||
|
||
// Insert the new notes to the i-th recipient's sets of notes. | ||
recipient_balances[i].insert(context, &mut recipient_note); | ||
|
||
// Get recipient encryption keys. | ||
let recipient_encryption_pub_key = get_public_key(recipients[i]); | ||
// Insert the new note to the owner's set of notes. | ||
balance.insert(context, note); | ||
|
||
// Remove this if statement if we can wrap this create_note function in an if statement. | ||
if note.value != 0 { | ||
// Emit the newly created encrypted note preimages via oracle calls. | ||
let mut recipient_encrypted_data = [0; VALUE_NOTE_LEN]; | ||
if recipient_note.value != 0 { | ||
recipient_encrypted_data = recipient_note.serialise(); | ||
}; | ||
// docs:start:encrypted | ||
let application_contract_address = (*context).this_address(); | ||
let note_storage_slot = balance.storage_slot; | ||
let encryption_pub_key = get_public_key(owner); | ||
let encrypted_data = (*note).serialise(); | ||
|
||
emit_encrypted_log( | ||
context, | ||
context.inputs.call_context.storage_contract_address, | ||
recipient_balances[i].storage_slot, | ||
recipient_encryption_pub_key, | ||
recipient_encrypted_data, | ||
application_contract_address, | ||
note_storage_slot, | ||
encryption_pub_key, | ||
encrypted_data, | ||
); | ||
// docs:end:encrypted | ||
} | ||
} | ||
} | ||
|
||
// Removes the note from the owner's set of notes. | ||
// Returns the value of the destroyed note. | ||
fn destroy_note( | ||
context: &mut PrivateContext, | ||
balance: Set<ValueNote, VALUE_NOTE_LEN>, | ||
owner: Field, | ||
note: ValueNote, | ||
) -> Field { | ||
// Ensure the note is actually owned by the owner (to prevent user from generating a valid proof while | ||
// spending someone else's notes). | ||
assert(note.owner == owner); | ||
|
||
balance.remove(context, note); | ||
|
||
note.value | ||
} |