Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove ARCHIVER_L1_START_BLOCK #8554

Merged
merged 3 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
// @todo #8018
uint256 public constant TIMELINESS_PROVING_IN_SLOTS = 100;

uint256 public immutable L1_BLOCK_AT_GENESIS;
IRegistry public immutable REGISTRY;
IInbox public immutable INBOX;
IOutbox public immutable OUTBOX;
Expand Down Expand Up @@ -84,6 +85,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
OUTBOX = new Outbox(address(this));
vkTreeRoot = _vkTreeRoot;
VERSION = 1;
L1_BLOCK_AT_GENESIS = block.number;

// Genesis block
blocks[0] = BlockLog({
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ interface IRollup {

function OUTBOX() external view returns (IOutbox);

function L1_BLOCK_AT_GENESIS() external view returns (uint256);

function propose(
bytes calldata _header,
bytes32 _archive,
Expand Down
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ members = [
"contracts/schnorr_hardcoded_account_contract",
"contracts/schnorr_single_key_account_contract",
"contracts/stateful_test_contract",
"contracts/spam_contract",
"contracts/test_contract",
"contracts/test_log_contract",
"contracts/token_contract",
Expand Down
10 changes: 10 additions & 0 deletions noir-projects/noir-contracts/contracts/spam_contract/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "spam_contract"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
value_note = { path = "../../../aztec-nr/value-note" }
token_portal_content_hash_lib = { path = "../token_portal_content_hash_lib" }
68 changes: 68 additions & 0 deletions noir-projects/noir-contracts/contracts/spam_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
mod types;

// A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests.
contract Spam {

use dep::aztec::{
prelude::{Map, AztecAddress, PublicMutable},
encrypted_logs::{encrypted_note_emission::encode_and_encrypt_note_with_keys_unconstrained},
keys::getters::get_current_public_keys,
protocol_types::{
hash::poseidon2_hash_with_separator,
constants::{
MAX_NOTE_HASHES_PER_CALL, MAX_NULLIFIERS_PER_CALL, GENERATOR_INDEX__NOTE_NULLIFIER,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL
}
}
};

use crate::types::{token_note::TokenNote, balance_set::BalanceSet};

#[aztec(storage)]
struct Storage {
balances: Map<AztecAddress, BalanceSet<TokenNote>>,
public_balances: Map<Field, PublicMutable<U128>>,
}

#[aztec(private)]
fn spam(nullifier_seed: Field, nullifier_count: u32, call_public: bool) {
let caller = context.msg_sender();
let caller_keys = get_current_public_keys(&mut context, caller);
let amount = U128::from_integer(1);

for _ in 0..MAX_NOTE_HASHES_PER_CALL {
storage.balances.at(caller).add(caller_keys.npk_m, U128::from_integer(amount)).emit(
encode_and_encrypt_note_with_keys_unconstrained(&mut context, caller_keys.ovpk_m, caller_keys.ivpk_m, caller)
);
}

for i in 0..MAX_NULLIFIERS_PER_CALL {
if (i < nullifier_count) {
context.push_nullifier(
poseidon2_hash_with_separator(
[nullifier_seed, i as Field],
GENERATOR_INDEX__NOTE_NULLIFIER as Field
)
);
}
}

if (call_public) {
Spam::at(context.this_address()).public_spam(0, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL).enqueue(&mut context);
Spam::at(context.this_address()).public_spam(
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX
).enqueue(&mut context);
}
}

#[aztec(public)]
#[aztec(internal)]
fn public_spam(start: u32, end: u32) {
let one = U128::from_integer(1);
for i in start..end {
let prev = storage.public_balances.at(i as Field).read();
storage.public_balances.at(i as Field).write(prev + one);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod balance_set;
mod token_note;
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// This file is copied from the token contract.
use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet};
use dep::aztec::{
context::{PrivateContext, UnconstrainedContext},
protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL,
note::note_emission::OuterNoteEmission, keys::public_keys::NpkM
};
use crate::types::token_note::OwnedNote;

struct BalanceSet<T, Context> {
set: PrivateSet<T, Context>,
}

impl<T, Context> BalanceSet<T, Context> {
pub fn new(context: Context, storage_slot: Field) -> Self {
assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
Self { set: PrivateSet::new(context, storage_slot) }
}
}

impl<T> BalanceSet<T, UnconstrainedContext> {
unconstrained pub fn balance_of<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(self: Self) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
self.balance_of_with_offset(0)
}

unconstrained pub fn balance_of_with_offset<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(
self: Self,
offset: u32
) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
let mut balance = U128::from_integer(0);
// docs:start:view_notes
let mut options = NoteViewerOptions::new();
let notes = self.set.view_notes(options.set_offset(offset));
// docs:end:view_notes
for i in 0..options.limit {
if i < notes.len() {
balance = balance + notes.get_unchecked(i).get_amount();
}
}
if (notes.len() == options.limit) {
balance = balance + self.balance_of_with_offset(offset + options.limit);
}

balance
}
}

impl<T> BalanceSet<T, &mut PrivateContext> {
pub fn add<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(
self: Self,
owner_npk_m: NpkM,
addend: U128
) -> OuterNoteEmission<T> where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
if addend == U128::from_integer(0) {
OuterNoteEmission::new(Option::none())
} else {
// We fetch the nullifier public key hash from the registry / from our PXE
let mut addend_note = T::new(addend, owner_npk_m.hash());

// docs:start:insert
OuterNoteEmission::new(Option::some(self.set.insert(&mut addend_note)))
// docs:end:insert
}
}

pub fn sub<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(
self: Self,
owner_npk_m: NpkM,
amount: U128
) -> OuterNoteEmission<T> where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL);

// try_sub may have substracted more or less than amount. We must ensure that we subtracted at least as much as
// we needed, and then create a new note for the owner for the change (if any).
assert(subtracted >= amount, "Balance too low");
self.add(owner_npk_m, subtracted - amount)
}

// Attempts to remove 'target_amount' from the owner's balance. try_sub returns how much was actually subtracted
// (i.e. the sum of the value of nullified notes), but this subtracted amount may be more or less than the target
// amount.
// This may seem odd, but is unfortunately unavoidable due to the number of notes available and their amounts being
// unknown. What try_sub does is a best-effort attempt to consume as few notes as possible that add up to more than
// `target_amount`.
// The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count
// scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of
// `try_sub` subtracting an amount smaller than `target_amount`.
pub fn try_sub<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(
self: Self,
target_amount: U128,
max_notes: u32
) -> U128 where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote + Eq {
// We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because
// we do not need to prove correct execution of the preprocessor.
// Because the `min_sum` notes is not constrained, users could choose to e.g. not call it. However, all this
// might result in is simply higher DA costs due to more nullifiers being emitted. Since we don't care
// about proving optimal note usage, we can save these constraints and make the circuit smaller.
let options = NoteGetterOptions::with_preprocessor(preprocess_notes_min_sum, target_amount).set_limit(max_notes);
let notes = self.set.pop_notes(options);

let mut subtracted = U128::from_integer(0);
for i in 0..options.limit {
if i < notes.len() {
let note = notes.get_unchecked(i);
subtracted = subtracted + note.get_amount();
}
}

subtracted
}
}

// Computes the partial sum of the notes array, stopping once 'min_sum' is reached. This can be used to minimize the
// number of notes read that add to some value, e.g. when transferring some amount of tokens.
// The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to
// 'min_sum' - all it does is remove extra notes if it does reach that value.
// Note that proper usage of this preprocessor requires for notes to be sorted in descending order.
pub fn preprocess_notes_min_sum<T, T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN>(
notes: [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL],
min_sum: U128
) -> [Option<T>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface<T_SERIALIZED_LEN, T_SERIALIZED_BYTES_LEN> + OwnedNote {
let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL];
let mut sum = U128::from_integer(0);
for i in 0..notes.len() {
// Because we process notes in retrieved order, notes need to be sorted in descending amount order for this
// filter to be useful. Consider a 'min_sum' of 4, and a set of notes with amounts [3, 2, 1, 1, 1, 1, 1]. If
// sorted in descending order, the filter will only choose the notes with values 3 and 2, but if sorted in
// ascending order it will choose 4 notes of value 1.
if notes[i].is_some() & sum < min_sum {
let note = notes[i].unwrap_unchecked();
selected[i] = Option::some(note);
sum = sum.add(note.get_amount());
}
}
selected
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use dep::aztec::{
generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot},
prelude::{NoteHeader, NoteInterface, PrivateContext},
protocol_types::{
constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH},
hash::poseidon2_hash_with_separator, traits::Serialize
},
note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand,
keys::getters::get_nsk_app
};
use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe};

trait OwnedNote {
fn new(amount: U128, owner_npk_m_hash: Field) -> Self;
fn get_amount(self) -> U128;
}

global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header.
global TOKEN_NOTE_BYTES_LEN: Field = 3 * 32 + 64;

// docs:start:TokenNote
#[aztec(note)]
struct TokenNote {
// The amount of tokens in the note
amount: U128,
// The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent.
npk_m_hash: Field,
// Randomness of the note to hide its contents
randomness: Field,
}
// docs:end:TokenNote

impl NoteInterface<TOKEN_NOTE_LEN, TOKEN_NOTE_BYTES_LEN> for TokenNote {
// docs:start:nullifier
fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field {
let secret = context.request_nsk_app(self.npk_m_hash);
poseidon2_hash_with_separator(
[
note_hash_for_nullify,
secret
],
GENERATOR_INDEX__NOTE_NULLIFIER as Field
)
}
// docs:end:nullifier

fn compute_nullifier_without_context(self) -> Field {
let note_hash_for_nullify = compute_note_hash_for_nullify(self);
let secret = get_nsk_app(self.npk_m_hash);
poseidon2_hash_with_separator(
[note_hash_for_nullify, secret],
GENERATOR_INDEX__NOTE_NULLIFIER
)
}

// docs:start:compute_note_hiding_point
fn compute_note_hiding_point(self) -> Point {
// We use the unsafe version because the multi_scalar_mul will constrain the scalars.
let amount_scalar = from_field_unsafe(self.amount.to_integer());
let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash);
let randomness_scalar = from_field_unsafe(self.randomness);
let slot_scalar = from_field_unsafe(self.header.storage_slot);
// We compute the note hiding point as:
// `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot`
// instead of using pedersen or poseidon2 because it allows us to privately add and subtract from amount
// in public by leveraging homomorphism.
multi_scalar_mul(
[G_amt, G_npk, G_rnd, G_slot],
[amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar]
)
}
// docs:end:compute_note_hiding_point
}

impl TokenNote {
// TODO: Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have
// to modify macros and all the related funcs in it.
fn to_note_hiding_point(self) -> TokenNoteHidingPoint {
TokenNoteHidingPoint::new(self.compute_note_hiding_point())
}
}

struct TokenNoteHidingPoint {
inner: Point
}

impl TokenNoteHidingPoint {
fn new(point: Point) -> Self {
Self { inner: point }
}

fn add_amount(&mut self, amount: U128) {
self.inner = multi_scalar_mul([G_amt], [from_field_unsafe(amount.to_integer())]) + self.inner;
}

fn add_npk_m_hash(&mut self, npk_m_hash: Field) {
self.inner = multi_scalar_mul([G_npk], [from_field_unsafe(npk_m_hash)]) + self.inner;
}

fn add_randomness(&mut self, randomness: Field) {
self.inner = multi_scalar_mul([G_rnd], [from_field_unsafe(randomness)]) + self.inner;
}

fn add_slot(&mut self, slot: Field) {
self.inner = multi_scalar_mul([G_slot], [from_field_unsafe(slot)]) + self.inner;
}

fn finalize(self) -> Field {
self.inner.x
}
}

impl Serialize<POINT_LENGTH> for TokenNoteHidingPoint {
fn serialize(self) -> [Field; POINT_LENGTH] {
self.inner.serialize()
}
}

impl Eq for TokenNote {
fn eq(self, other: Self) -> bool {
(self.amount == other.amount)
& (self.npk_m_hash == other.npk_m_hash)
& (self.randomness == other.randomness)
}
}

impl OwnedNote for TokenNote {
fn new(amount: U128, owner_npk_m_hash: Field) -> Self {
Self { amount, npk_m_hash: owner_npk_m_hash, randomness: unsafe_rand(), header: NoteHeader::empty() }
}

fn get_amount(self) -> U128 {
self.amount
}
}
Loading
Loading