Skip to content

Commit

Permalink
feat: add the storage layout to the contract artifact (#5952)
Browse files Browse the repository at this point in the history
Fixes #5947 and includes the notes as well. The notes was part of the
contract object, but not directly exposed on the artifact itself.

Needed to fix a few additional things to make it work:
- The `deploy_contract` function was broken (old call flow), it cannot
do the new flow because that would be a circular dependency.
- Using the `storageLayout` values to get the `storageSlot` that we are
using in multiple tests
- Using the `notes` values to get the `noteTypeId` that we are using in
multiple tests
- Removed `lodash.uniqby` as the dependency was not needed after this.
  • Loading branch information
LHerskind authored and TomAFrench committed Apr 24, 2024
1 parent 15204cc commit 83a1721
Show file tree
Hide file tree
Showing 19 changed files with 182 additions and 117 deletions.
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/api/abi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { ContractArtifact, FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
export { loadContractArtifact } from '@aztec/types/abi';
export { loadContractArtifact, contractArtifactToBuffer, contractArtifactFromBuffer } from '@aztec/types/abi';
export { NoirCompiledContract } from '@aztec/types/noir';
2 changes: 2 additions & 0 deletions yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ describe('Contract Class', () => {
globals: {},
},
fileMap: {},
storageLayout: {},
notes: {},
};

beforeEach(() => {
Expand Down
38 changes: 8 additions & 30 deletions yarn-project/aztec.js/src/contract/contract_base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { type Fr, computePartialAddress } from '@aztec/circuits.js';
import { type ContractArtifact, type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi';
import { computePartialAddress } from '@aztec/circuits.js';
import {
type ContractArtifact,
type ContractNote,
type FieldLayout,
type FunctionArtifact,
FunctionSelector,
} from '@aztec/foundation/abi';
import { type ContractInstanceWithAddress } from '@aztec/types/contracts';

import { type Wallet } from '../account/index.js';
Expand All @@ -16,34 +22,6 @@ export type ContractMethod = ((...args: any[]) => ContractFunctionInteraction) &
readonly selector: FunctionSelector;
};

/**
* Type representing a field layout in the storage of a contract.
*/
type FieldLayout = {
/**
* Slot in which the field is stored.
*/
slot: Fr;
/**
* Type being stored at the slot
*/
typ: string;
};

/**
* Type representing a note in use in the contract.
*/
type ContractNote = {
/**
* Note identifier
*/
id: Fr;
/**
* Type of the note
*/
typ: string;
};

/**
* Type representing the storage layout of a contract.
*/
Expand Down
2 changes: 0 additions & 2 deletions yarn-project/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"fs-extra": "^11.1.1",
"lodash.camelcase": "^4.3.0",
"lodash.capitalize": "^4.2.1",
"lodash.uniqby": "^4.7.0",
"memfs": "^4.6.0",
"pako": "^2.1.0",
"semver": "^7.5.4",
Expand All @@ -71,7 +70,6 @@
"@types/jest": "^29.5.0",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.capitalize": "^4.2.7",
"@types/lodash.uniqby": "^4.7.9",
"@types/node": "^18.7.23",
"@types/pako": "^2.0.0",
"@types/semver": "^7.5.4",
Expand Down
71 changes: 29 additions & 42 deletions yarn-project/builder/src/contract-interface-gen/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import {
type ABIParameter,
type BasicValue,
type ContractArtifact,
type FunctionArtifact,
type IntegerValue,
type StructValue,
type TupleValue,
type TypedStructFieldValue,
getDefaultInitializer,
isAztecAddressStruct,
isEthAddressStruct,
isFunctionSelectorStruct,
isWrappedFieldStruct,
} from '@aztec/foundation/abi';

import uniqBy from 'lodash.uniqby';

/**
* Returns the corresponding typescript type for a given Noir type.
* @param type - The input Noir type.
Expand Down Expand Up @@ -192,64 +185,58 @@ function generateAbiStatement(name: string, artifactImportPath: string) {
* @param input - The contract artifact.
*/
function generateStorageLayoutGetter(input: ContractArtifact) {
const storage = input.outputs.globals.storage ? (input.outputs.globals.storage[0] as StructValue) : { fields: [] };
const storageFields = storage.fields as TypedStructFieldValue<StructValue>[];
const storageFieldsUnionType = storageFields.map(f => `'${f.name}'`).join(' | ');
const layout = storageFields
const entries = Object.entries(input.storageLayout);

if (entries.length === 0) {
return '';
}

const storageFieldsUnionType = entries.map(([name]) => `'${name}'`).join(' | ');
const layout = entries
.map(
({
name,
value: {
fields: [slot, typ],
},
}) =>
([name, { slot, typ }]) =>
`${name}: {
slot: new Fr(${(slot.value as IntegerValue).value}n),
typ: "${(typ.value as BasicValue<'string', string>).value}",
}
`,
slot: new Fr(${slot.toBigInt()}n),
typ: "${typ}",
}`,
)
.join(',\n');
return storageFields.length > 0
? `
public static get storage(): ContractStorageLayout<${storageFieldsUnionType}> {

return `public static get storage(): ContractStorageLayout<${storageFieldsUnionType}> {
return {
${layout}
} as ContractStorageLayout<${storageFieldsUnionType}>;
}
`
: '';
`;
}

/**
* Generates a getter for the contract notes
* @param input - The contract artifact.
*/
function generateNotesGetter(input: ContractArtifact) {
const notes = input.outputs.globals.notes
? uniqBy(input.outputs.globals.notes as TupleValue[], n => (n.fields[1] as BasicValue<'string', string>).value)
: [];
const notesUnionType = notes.map(n => `'${(n.fields[1] as BasicValue<'string', string>).value}'`).join(' | ');
const entries = Object.entries(input.notes);

const noteMetadata = notes
if (entries.length === 0) {
return '';
}

const notesUnionType = entries.map(([name]) => `'${name}'`).join(' | ');
const noteMetadata = entries
.map(
({ fields: [id, typ] }) =>
`${(typ as BasicValue<'string', string>).value}: {
id: new Fr(${(id as IntegerValue).value}n),
}
`,
([name, { id }]) =>
`${name}: {
id: new Fr(${id.toBigInt()}n),
}`,
)
.join(',\n');
return notes.length > 0
? `
public static get notes(): ContractNotes<${notesUnionType}> {
const notes = this.artifact.outputs.globals.notes ? (this.artifact.outputs.globals.notes as any) : [];

return `public static get notes(): ContractNotes<${notesUnionType}> {
return {
${noteMetadata}
} as ContractNotes<${notesUnionType}>;
}
`
: '';
`;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuit-types/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ export const randomContractArtifact = (): ContractArtifact => ({
globals: {},
},
fileMap: {},
storageLayout: {},
notes: {},
});

export const randomContractInstanceWithAddress = (opts: { contractClassId?: Fr } = {}): ContractInstanceWithAddress =>
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuits.js/src/contract/artifact_hash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ describe('ArtifactHash', () => {
globals: {},
structs: {},
},
storageLayout: {},
notes: {},
};
expect(computeArtifactHash(emptyArtifact).toString()).toMatchInlineSnapshot(
`"0x0dea64e7fa0688017f77bcb7075485485afb4a5f1f8508483398869439f82fdf"`,
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/e2e_account_init_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ describe('e2e_fees_account_init', () => {
});

async function addTransparentNoteToPxe(owner: AztecAddress, amount: bigint, secretHash: Fr, txHash: TxHash) {
const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5.
const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // TransparentNote
const storageSlot = bananaCoin.artifact.storageLayout['pending_shields'].slot;
const noteTypeId = bananaCoin.artifact.notes['TransparentNote'].id;

const note = new Note([new Fr(amount), secretHash]);
// this note isn't encrypted but we need to provide a registered public key
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/sample-dapp/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ async function mintPrivateFunds(pxe) {
const secretHash = await computeSecretHash(secret);
const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait();

const storageSlot = new Fr(5);
const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // TransparentNote
const storageSlot = token.artifact.storageLayout['pending_shields'].slot;
const noteTypeId = token.artifact.notes['TransparentNote'].id;

const note = new Note([new Fr(mintAmount), secretHash]);
const extendedNote = new ExtendedNote(
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/end-to-end/src/sample-dapp/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe('token', () => {
const secretHash = await computeSecretHash(secret);
const receipt = await token.methods.mint_private(initialBalance, secretHash).send().wait();

const storageSlot = new Fr(5);
const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // TransparentNote
const storageSlot = token.artifact.storageLayout['pending_shields'].slot;
const noteTypeId = token.artifact.notes['TransparentNote'].id;
const note = new Note([new Fr(initialBalance), secretHash]);
const extendedNote = new ExtendedNote(
note,
Expand Down
10 changes: 5 additions & 5 deletions yarn-project/end-to-end/src/shared/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@ export const browserTestSuite = (
INITIAL_TEST_SIGNING_KEYS,
INITIAL_TEST_ACCOUNT_SALTS,
Buffer,
contractArtifactFromBuffer,
} = window.AztecJs;
// We serialize the artifact since buffers (used for bytecode) do not cross well from one realm to another
const TokenContractArtifact = JSON.parse(
Buffer.from(serializedTokenContractArtifact, 'base64').toString('utf-8'),
(key, value) => (key === 'bytecode' && typeof value === 'string' ? Buffer.from(value, 'base64') : value),
const TokenContractArtifact = contractArtifactFromBuffer(
Buffer.from(serializedTokenContractArtifact, 'base64'),
);
const pxe = createPXEClient(rpcUrl!);

Expand Down Expand Up @@ -264,9 +264,9 @@ export const browserTestSuite = (
const secretHash = computeSecretHash(secret);
const mintPrivateReceipt = await token.methods.mint_private(initialBalance, secretHash).send().wait();

const storageSlot = new Fr(5);
const storageSlot = token.artifact.storageLayout['pending_shields'].slot;

const noteTypeId = new Fr(84114971101151129711410111011678111116101n);
const noteTypeId = token.artifact.notes['TransparentNote'].id;
const note = new Note([new Fr(initialBalance), secretHash]);
const extendedNote = new ExtendedNote(
note,
Expand Down
37 changes: 37 additions & 0 deletions yarn-project/foundation/src/abi/abi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { inflate } from 'pako';

import { type Fr } from '../fields/fields.js';
import { type FunctionSelector } from './function_selector.js';

/**
Expand Down Expand Up @@ -267,6 +268,34 @@ export type DebugFileMap = Record<
}
>;

/**
* Type representing a note in use in the contract.
*/
export type ContractNote = {
/**
* Note identifier
*/
id: Fr;
/**
* Type of the note (e.g., 'TransparentNote')
*/
typ: string;
};

/**
* Type representing a field layout in the storage of a contract.
*/
export type FieldLayout = {
/**
* Slot in which the field is stored.
*/
slot: Fr;
/**
* Type being stored at the slot (e.g., 'Map<AztecAddress, PublicMutable<U128>>')
*/
typ: string;
};

/**
* Defines artifact of a contract.
*/
Expand All @@ -292,6 +321,14 @@ export interface ContractArtifact {
structs: Record<string, AbiType[]>;
globals: Record<string, AbiValue[]>;
};
/**
* Storage layout
*/
storageLayout: Record<string, FieldLayout>;
/**
* The notes used in the contract.
*/
notes: Record<string, ContractNote>;

/**
* The map of file ID to the source code and path of the file.
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/p2p/src/service/discv5_service.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { jest } from '@jest/globals';
import type { PeerId } from '@libp2p/interface';

import { BootstrapNode } from '../bootstrap/bootstrap.js';
Expand All @@ -21,6 +22,8 @@ const waitForPeers = (node: DiscV5Service, expectedCount: number): Promise<void>
};

describe('Discv5Service', () => {
jest.setTimeout(10_000);

let bootNode: BootstrapNode;
let bootNodePeerId: PeerId;
let port = 1234;
Expand Down
Loading

0 comments on commit 83a1721

Please sign in to comment.