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: Public view functions (unconstrained can read public storage) #1421

Merged
merged 8 commits into from
Aug 7, 2023

Conversation

LHerskind
Copy link
Contributor

@LHerskind LHerskind commented Aug 4, 2023

Addresses #1375 by giving the unconstrained execution access to public storage through the node.

Good things:

  • Makes it simpler to read values

Bad things:

  • Because the simulator is much slower than reading the value, makes testing slower 😭.

The return value from view functions are an array of bigint but should be altered to be following the abi specification instead for easy access.

Checklist:

Remove the checklist to signal you've completed it. Enable auto-merge if the PR is ready to merge.

  • If the pull request requires a cryptography review (e.g. cryptographic algorithm implementations) I have added the 'crypto' tag.
  • I have reviewed my diff in github, line by line and removed unexpected formatting changes, testing logs, or commented-out code.
  • Every change is related to the PR description.
  • I have linked this pull request to relevant issues (if any exist).

@LHerskind LHerskind force-pushed the lh/1375-public-view branch from a8128a6 to 7e7b88c Compare August 4, 2023 12:50
@ludamad
Copy link
Collaborator

ludamad commented Aug 4, 2023

Would this need later constraints to be used in practice?

@LHerskind
Copy link
Contributor Author

Would this need later constraints to be used in practice?

I don't think so. The intention is just to have unconstrained that can read public storage, as you can then use it to read balances from public without having to compute the storage slots manually for tests and wallets and whatever uses the contracts.

@LHerskind LHerskind force-pushed the lh/1375-public-view branch from 0c21ce6 to faa0903 Compare August 4, 2023 14:44
@LHerskind LHerskind marked this pull request as ready for review August 4, 2023 14:56
@@ -306,4 +306,22 @@ contract Lending {
debt_loc.write(static_debt - amount);
1
}

unconstrained fn getTot(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider a better name than tot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this Pr. The struct is called tot so just used that naming here. Planning to rewrite most of the lending contract when time permits.

owner: Field,
) -> Field {
let storage = Storage::init();
storage.public_balances.at(owner).read()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume if the user has nothing, this returns a 0 as opposed to undefined or whatever?

@@ -95,8 +91,8 @@ describe('e2e_public_token_contract', () => {
expect(receipts.map(r => r.status)).toEqual(times(3, () => TxStatus.MINED));
expect(receipts.map(r => r.blockNumber)).toEqual(times(3, () => receipts[0].blockNumber));

const balance = await cc.l2.loadPublic(contract.address, cc.l2.computeSlotInMap(balanceSlot, recipient.toField()));
expect(balance.value).toBe(mintAmount * 3n);
const balance = (await contract.methods.publicBalanceOf(recipient.toField()).view({ from: recipient }))[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a github issue to not have this return an array?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iAmMichaelConnor have a fix in one of his branches.

@@ -54,6 +56,26 @@ export class UnconstrainedFunctionExecution {
},
getL1ToL2Message: ([msgKey]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),
getCommitment: ([commitment]) => this.context.getCommitment(this.contractAddress, commitment),
storageRead: async ([slot], [numberOfElements]) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the oracle call in noir that tells the VM to call storageRead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? It is the same as with public execution? The .read() ends up calling oracles in here.
https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-libs/noir-aztec/src/oracle/storage.nr

@@ -101,6 +102,7 @@ export class AcirSimulator {
contractAddress: AztecAddress,
portalContractAddress: EthAddress,
historicRoots: PrivateHistoricTreeRoots,
aztecNode: AztecNode | undefined = undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicer is

Suggested change
aztecNode: AztecNode | undefined = undefined,
aztecNode?: AztecNode,

* @returns The return values of the executed function.
*/
public async run(): Promise<any[]> {
public async run(aztecNode: AztecNode | undefined = undefined): Promise<any[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ibid

@@ -54,6 +56,26 @@ export class UnconstrainedFunctionExecution {
},
getL1ToL2Message: ([msgKey]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),
getCommitment: ([commitment]) => this.context.getCommitment(this.contractAddress, commitment),
storageRead: async ([slot], [numberOfElements]) => {
if (aztecNode === undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (aztecNode === undefined) {
if (!aztecNode) {

this.log(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
throw new Error(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
}
const frValue = Fr.fromBuffer(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toACVMField can take in a Buffer as an arg. It would be cleaner to just store values as buffers, they can then be converted in one step at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly for consistency between the implementations in the executor and here. Also just made it super simple to handle the logging 🤷

const getStorageSnapshot = async (contract: LendingContract, aztecNode: AztecRPC, account: Account) => {
const storageValues: { [key: string]: Fr } = {};
const accountKey = await account.key();
const toFields = (res: any) => res[0].map((v: number | bigint | Fr) => new Fr(v));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

res should denote an array here

@LHerskind LHerskind linked an issue Aug 7, 2023 that may be closed by this pull request
Copy link
Member

@Maddiaa0 Maddiaa0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, tiny nit around code reuse, change if you want, otherwise shipit

Comment on lines 59 to 85
storageRead: async ([slot], [numberOfElements]) => {
if (!aztecNode) {
this.log(`Aztec node is undefined, cannot read public storage`);
throw new Error(`Aztec node is undefined, cannot read public storage`);
}
const startStorageSlot = fromACVMField(slot);
const values = [];
for (let i = 0; i < Number(numberOfElements); i++) {
const storageSlot = startStorageSlot.value + BigInt(i);
const value = await aztecNode.getPublicStorageAt(this.contractAddress, storageSlot);
if (value === undefined) {
this.log(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
throw new Error(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
}
const frValue = Fr.fromBuffer(value);
this.log(`Oracle storage read: slot=${storageSlot.toString()} value=${frValue.toString()}`);
values.push(frValue);
}
return values.map(v => toACVMField(v));
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the logs here could be tidied up, there is alot of repeated text

Suggested change
storageRead: async ([slot], [numberOfElements]) => {
if (!aztecNode) {
this.log(`Aztec node is undefined, cannot read public storage`);
throw new Error(`Aztec node is undefined, cannot read public storage`);
}
const startStorageSlot = fromACVMField(slot);
const values = [];
for (let i = 0; i < Number(numberOfElements); i++) {
const storageSlot = startStorageSlot.value + BigInt(i);
const value = await aztecNode.getPublicStorageAt(this.contractAddress, storageSlot);
if (value === undefined) {
this.log(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
throw new Error(`Oracle storage read: slot=${storageSlot.toString()} value=undefined`);
}
const frValue = Fr.fromBuffer(value);
this.log(`Oracle storage read: slot=${storageSlot.toString()} value=${frValue.toString()}`);
values.push(frValue);
}
return values.map(v => toACVMField(v));
},
storageRead: async ([slot], [numberOfElements]) => {
if (!aztecNode) {
const errorLog = "Aztec node is undefined, cannot read public storage";
this.log(errorLog);
throw new Error(errorLog);
}
const makeLog = (slot, value) => `Oracle storage read: slot=${slot.toString()} value=${value.toString()}`;
const startStorageSlot = fromACVMField(slot);
const values = [];
for (let i = 0; i < Number(numberOfElements); i++) {
const storageSlot = startStorageSlot.value + BigInt(i);
const value = await aztecNode.getPublicStorageAt(this.contractAddress, storageSlot);
if (value === undefined) {
this.log(makeLog(storageSlot, "undefined"));
throw new Error(makeLog(storageSlot, "undefined"));
}
const frValue = Fr.fromBuffer(value);
this.log(makeLog(storageSlot, frValue.toString()));
values.push(frValue);
}
return values.map(v =

@LHerskind LHerskind force-pushed the lh/1375-public-view branch from 0d8d779 to 3efb52d Compare August 7, 2023 20:49
@LHerskind LHerskind merged commit 912c1b4 into master Aug 7, 2023
@LHerskind LHerskind deleted the lh/1375-public-view branch August 7, 2023 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

feat: Support unconstrained public functions
4 participants