-
Notifications
You must be signed in to change notification settings - Fork 295
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
Conversation
a8128a6
to
7e7b88c
Compare
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. |
0c21ce6
to
faa0903
Compare
@@ -306,4 +306,22 @@ contract Lending { | |||
debt_loc.write(static_debt - amount); | |||
1 | |||
} | |||
|
|||
unconstrained fn getTot( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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]) => { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nicer is
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[]> { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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
There was a problem hiding this 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
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)); | ||
}, |
There was a problem hiding this comment.
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
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 = |
0d8d779
to
3efb52d
Compare
Addresses #1375 by giving the unconstrained execution access to public storage through the node.
Good things:
Bad things:
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.