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

feat: add shared mutable access from external contract #5689 #5758

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 noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod shared_mutable;
mod scheduled_value_change;
mod shared_mutable_private_getter;

use shared_mutable::SharedMutable;
use shared_mutable_private_getter::SharedMutablePrivateGetter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use dep::protocol_types::{hash::pedersen_hash, traits::FromField, address::AztecAddress};

use crate::context::{PrivateContext, Context};
use crate::history::public_storage::public_storage_historical_read;
use crate::public_storage;
use crate::state_vars::{storage::Storage, shared_mutable::scheduled_value_change::ScheduledValueChange};

struct SharedMutablePrivateGetter<T, DELAY> {
context: PrivateContext,
// The contract address of the contract we want to read from
contract_address: AztecAddress,
// The storage slot where the SharedMutable is stored on the other contract
storage_slot: Field,
}

impl<T, DELAY> SharedMutablePrivateGetter<T, DELAY> {
pub fn new(
context: PrivateContext,
contract_address: AztecAddress,
storage_slot: Field,
) -> Self {
assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
assert(contract_address.to_field() != 0, "Contract address cannot be 0");
Self { context, contract_address, storage_slot }
}

pub fn get_current_value_in_private(self) -> T where T: FromField {
let mut context = self.context;

let (scheduled_value_change, historical_block_number) = self.historical_read_from_public_storage(context);
let block_horizon = scheduled_value_change.get_block_horizon(historical_block_number);

// We prevent this transaction from being included in any block after the block horizon, ensuring that the
// historical public value matches the current one, since it can only change after the horizon.
context.set_tx_max_block_number(block_horizon);
scheduled_value_change.get_current_at(historical_block_number)
}

fn historical_read_from_public_storage(
self,
context: PrivateContext
) -> (ScheduledValueChange<T, DELAY>, u32) where T: FromField {
let derived_slot = self.get_derived_storage_slot();

// Ideally the following would be simply public_storage::read_historical, but we can't implement that yet.
let mut raw_fields = [0; 3];
for i in 0..3 {
raw_fields[i] = public_storage_historical_read(
context,
derived_slot + i as Field,
self.contract_address
);
}

let scheduled_value: ScheduledValueChange<T, DELAY> = ScheduledValueChange::deserialize(raw_fields);
let historical_block_number = context.historical_header.global_variables.block_number as u32;

(scheduled_value, historical_block_number)
}

fn get_derived_storage_slot(self) -> Field {
// Since we're actually storing three values (a ScheduledValueChange struct), we hash the storage slot to get a
// unique location in which we can safely store as much data as we need. This could be removed if we informed
// the slot allocator of how much space we need so that proper padding could be added.
// See https://github.com/AztecProtocol/aztec-packages/issues/5492
pedersen_hash([self.storage_slot, 0], 0)
}
}
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ members = [
"contracts/schnorr_hardcoded_account_contract",
"contracts/schnorr_single_key_account_contract",
"contracts/slow_tree_contract",
"contracts/state_vars_contract",
"contracts/stateful_test_contract",
"contracts/test_contract",
"contracts/token_contract",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "state_vars_contract"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { path = "../../../aztec-nr/aztec" }
authwit = { path = "../../../aztec-nr/authwit" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
contract StateVars {
use dep::aztec::prelude::{
AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, NoteViewerOptions,
PrivateContext, Map, PublicMutable, PublicImmutable, PrivateMutable, PrivateImmutable,
PrivateSet, SharedImmutable
};
use dep::aztec::protocol_types::traits::{Deserialize, Serialize};

use dep::aztec::{
state_vars::shared_mutable::SharedMutablePrivateGetter,
context::{PublicContext, Context},
log::{emit_unencrypted_log_from_private},
};

#[aztec(private)]
fn test_shared_mutable_private_getter(
contract_address_to_read: AztecAddress,
storage_slot_of_shared_mutable: Field,
) {
// It's a bit wonky because we need to know the delay for get_current_value_in_private to work correctly
let test: SharedMutablePrivateGetter<AztecAddress, 5> = SharedMutablePrivateGetter::new(context, contract_address_to_read, storage_slot_of_shared_mutable);
let authorized = test.get_current_value_in_private();

emit_unencrypted_log_from_private(&mut context, authorized);
}
}
43 changes: 40 additions & 3 deletions yarn-project/end-to-end/src/e2e_state_vars.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Wallet } from '@aztec/aztec.js';
import { DocsExampleContract } from '@aztec/noir-contracts.js';
import { AztecAddress, Fr, type PXE, type Wallet } from '@aztec/aztec.js';
import { AuthContract, DocsExampleContract, StateVarsContract } from '@aztec/noir-contracts.js';

import { jest } from '@jest/globals';

Expand All @@ -8,6 +8,7 @@ import { setup } from './fixtures/utils.js';
const TIMEOUT = 100_000;

describe('e2e_state_vars', () => {
let pxe: PXE;
jest.setTimeout(TIMEOUT);

let wallet: Wallet;
Expand All @@ -19,7 +20,7 @@ describe('e2e_state_vars', () => {
const RANDOMNESS = 2n;

beforeAll(async () => {
({ teardown, wallet } = await setup());
({ teardown, wallet, pxe } = await setup(2));
contract = await DocsExampleContract.deploy(wallet).send().deployed();
}, 30_000);

Expand Down Expand Up @@ -217,4 +218,40 @@ describe('e2e_state_vars', () => {
expect(randomness).toEqual(RANDOMNESS);
});
});

describe('SharedMutablePrivateGetter', () => {
let authContract: AuthContract;
let stateVarsContract: StateVarsContract;

const delay = async (blocks: number) => {
for (let i = 0; i < blocks; i++) {
await authContract.methods.get_authorized().send().wait();
}
};

beforeAll(async () => {
stateVarsContract = await StateVarsContract.deploy(wallet).send().deployed();
authContract = await AuthContract.deploy(wallet, wallet.getAddress()).send().deployed();
}, 30_000);

it('should set authorized in auth contract', async () => {
await authContract
.withWallet(wallet)
.methods.set_authorized(AztecAddress.fromField(new Fr(6969696969)))
.send()
.wait();
});

it('checks authorized from auth contract from state vars contract', async () => {
await delay(5);

const { txHash } = await stateVarsContract.methods
.test_shared_mutable_private_getter(authContract.address, 2)
.send()
.wait();

const rawLogs = await pxe.getUnencryptedLogs({ txHash });
expect(Fr.fromBuffer(rawLogs.logs[0].log.data)).toEqual(new Fr(6969696969));
});
});
});
Loading