Skip to content

Commit

Permalink
feat: public executor fetches isInternal (#1324)
Browse files Browse the repository at this point in the history
# Description

Fixes #1304 by including the `isInternal` flag in
`EncodedContractFunction` which is broadcasted on chain. This allows the
archiver to fetch it, and it can then be fetched by extending the
`PublicContractsDB`
  • Loading branch information
LHerskind authored Aug 1, 2023
1 parent 1b7c278 commit 45c1f51
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 55 deletions.
8 changes: 8 additions & 0 deletions yarn-project/acir-simulator/src/public/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export interface PublicContractsDB {
*/
getBytecode(address: AztecAddress, functionSelector: Buffer): Promise<Buffer | undefined>;

/**
* Returns whether a function is internal or not.
* @param address - The contract address that owns this function.
* @param functionSelector - The selector for the function.
* @returns The `isInternal` flag found, undefined if not found.
*/
getIsInternal(address: AztecAddress, functionSelector: Buffer): Promise<boolean | undefined>;

/**
* Returns the portal contract address for an L2 address.
* @param address - The L2 contract address.
Expand Down
12 changes: 9 additions & 3 deletions yarn-project/acir-simulator/src/public/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,15 @@ export class PublicExecutor {
globalVariables: GlobalVariables,
) {
const portalAddress = (await this.contractsDb.getPortalContractAddress(targetContractAddress)) ?? EthAddress.ZERO;
// @todo @lherskind - Need to make this data accessible (See issue #1200)
//const abi = await this.contractsDb.getFunctionABI(targetContractAddress, targetFunctionSelector);
const isInternal = false;
const isInternal = await this.contractsDb.getIsInternal(targetContractAddress, targetFunctionSelector);
if (isInternal === undefined) {
throw new Error(
`ERR: ContractsDb don't contain isInternal for ${targetContractAddress.toString()}:${targetFunctionSelector.toString(
'hex',
)}. Defaulting to false.`,
);
}

const functionData = new FunctionData(targetFunctionSelector, isInternal, false, false);

const callContext = CallContext.from({
Expand Down
114 changes: 65 additions & 49 deletions yarn-project/acir-simulator/src/public/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('ACIR public execution simulator', () => {

commitmentsDb.getTreeRoots.mockReturnValue(PrivateHistoricTreeRoots.empty());
executor = new PublicExecutor(publicState, publicContracts, commitmentsDb);
});
}, 10000);

describe('PublicToken contract', () => {
let recipient: AztecAddress;
Expand Down Expand Up @@ -187,58 +187,74 @@ describe('ACIR public execution simulator', () => {
});

describe('Parent/Child contracts', () => {
it('calls the public entry point in the parent', async () => {
const parentContractAddress = AztecAddress.random();
const parentEntryPointFn = ParentContractAbi.functions.find(f => f.name === 'pubEntryPoint')!;
const parentEntryPointFnSelector = keccak(Buffer.from(parentEntryPointFn.name)).subarray(0, 4);

const childContractAddress = AztecAddress.random();
const childValueFn = ChildContractAbi.functions.find(f => f.name === 'pubValue')!;
const childValueFnSelector = keccak(Buffer.from(childValueFn.name)).subarray(0, 4);

const initialValue = 3n;

const functionData = new FunctionData(parentEntryPointFnSelector, false, false, false);
const args = encodeArguments(parentEntryPointFn, [
childContractAddress.toField().value,
toBigInt(childValueFnSelector),
initialValue,
]);
it.each([false, true, undefined])(
'calls the public entry point in the parent',
async isInternal => {
const parentContractAddress = AztecAddress.random();
const parentEntryPointFn = ParentContractAbi.functions.find(f => f.name === 'pubEntryPoint')!;
const parentEntryPointFnSelector = keccak(Buffer.from(parentEntryPointFn.name)).subarray(0, 4);

const childContractAddress = AztecAddress.random();
const childValueFn = ChildContractAbi.functions.find(f => f.name === 'pubValue')!;
const childValueFnSelector = keccak(Buffer.from(childValueFn.name)).subarray(0, 4);

const initialValue = 3n;

const functionData = new FunctionData(parentEntryPointFnSelector, isInternal ?? false, false, false);
const args = encodeArguments(parentEntryPointFn, [
childContractAddress.toField().value,
toBigInt(childValueFnSelector),
initialValue,
]);

const callContext = CallContext.from({
msgSender: AztecAddress.random(),
storageContractAddress: parentContractAddress,
portalContractAddress: EthAddress.random(),
isContractDeployment: false,
isDelegateCall: false,
isStaticCall: false,
});
const callContext = CallContext.from({
msgSender: AztecAddress.random(),
storageContractAddress: parentContractAddress,
portalContractAddress: EthAddress.random(),
isContractDeployment: false,
isDelegateCall: false,
isStaticCall: false,
});

// eslint-disable-next-line require-await
publicContracts.getBytecode.mockImplementation(async (addr: AztecAddress, selector: Buffer) => {
if (addr.equals(parentContractAddress) && selector.equals(parentEntryPointFnSelector)) {
return Buffer.from(parentEntryPointFn.bytecode, 'base64');
} else if (addr.equals(childContractAddress) && selector.equals(childValueFnSelector)) {
return Buffer.from(childValueFn.bytecode, 'base64');
} else {
return undefined;
}
});

publicContracts.getIsInternal.mockImplementation(() => {
return Promise.resolve(isInternal);
});

const execution: PublicExecution = { contractAddress: parentContractAddress, functionData, args, callContext };
const globalVariables = new GlobalVariables(new Fr(69), new Fr(420), new Fr(1), new Fr(7));

// eslint-disable-next-line require-await
publicContracts.getBytecode.mockImplementation(async (addr: AztecAddress, selector: Buffer) => {
if (addr.equals(parentContractAddress) && selector.equals(parentEntryPointFnSelector)) {
return Buffer.from(parentEntryPointFn.bytecode, 'base64');
} else if (addr.equals(childContractAddress) && selector.equals(childValueFnSelector)) {
return Buffer.from(childValueFn.bytecode, 'base64');
if (isInternal === undefined) {
// The error is don't seem to be propagated, but can see it in the logger.
await expect(executor.execute(execution, globalVariables)).rejects.toBe(
'Error awaiting `foreign_call_handler`: Unknown',
);
} else {
return undefined;
const result = await executor.execute(execution, globalVariables);

expect(result.returnValues).toEqual([
new Fr(
initialValue +
globalVariables.chainId.value +
globalVariables.version.value +
globalVariables.blockNumber.value +
globalVariables.timestamp.value,
),
]);
}
});

const execution: PublicExecution = { contractAddress: parentContractAddress, functionData, args, callContext };
const globalVariables = new GlobalVariables(new Fr(69), new Fr(420), new Fr(1), new Fr(7));
const result = await executor.execute(execution, globalVariables);

expect(result.returnValues).toEqual([
new Fr(
initialValue +
globalVariables.chainId.value +
globalVariables.version.value +
globalVariables.blockNumber.value +
globalVariables.timestamp.value,
),
]);
}, 20000);
},
20_000,
);
});

describe('Public -> Private / Cross Chain messaging', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class ContractsDataSourcePublicDB implements PublicContractsDB {
async getBytecode(address: AztecAddress, functionSelector: Buffer): Promise<Buffer | undefined> {
return (await this.db.getPublicFunction(address, functionSelector))?.bytecode;
}
async getIsInternal(address: AztecAddress, functionSelector: Buffer): Promise<boolean | undefined> {
return (await this.db.getPublicFunction(address, functionSelector))?.isInternal;
}
async getPortalContractAddress(address: AztecAddress): Promise<EthAddress | undefined> {
return (await this.db.getL2ContractInfo(address))?.portalContractAddress;
}
Expand Down
1 change: 1 addition & 0 deletions yarn-project/types/src/contract_dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function getNewContractPublicFunctions(newContract: ContractDao) {
fn =>
new EncodedContractFunction(
generateFunctionSelector(fn.name, fn.parameters),
fn.isInternal ?? false,
Buffer.from(fn.bytecode, 'base64'),
),
);
Expand Down
11 changes: 8 additions & 3 deletions yarn-project/types/src/contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class EncodedContractFunction {
* The function selector.
*/
public functionSelector: Buffer,
/**
* Whether the function is internal.
*/
public isInternal: boolean,
/**
* The function bytecode.
*/
Expand All @@ -72,7 +76,7 @@ export class EncodedContractFunction {
*/
toBuffer(): Buffer {
const bytecodeBuf = Buffer.concat([numToInt32BE(this.bytecode.length), this.bytecode]);
return serializeToBuffer(this.functionSelector, bytecodeBuf);
return serializeToBuffer(this.functionSelector, this.isInternal, bytecodeBuf);
}

/**
Expand All @@ -83,15 +87,16 @@ export class EncodedContractFunction {
static fromBuffer(buffer: Buffer | BufferReader): EncodedContractFunction {
const reader = BufferReader.asReader(buffer);
const fnSelector = reader.readBytes(FUNCTION_SELECTOR_NUM_BYTES);
return new EncodedContractFunction(fnSelector, reader.readBuffer());
const isInternal = reader.readBoolean();
return new EncodedContractFunction(fnSelector, isInternal, reader.readBuffer());
}

/**
* Creates a random contract function.
* @returns A random contract function.
*/
static random(): EncodedContractFunction {
return new EncodedContractFunction(randomBytes(4), randomBytes(64));
return new EncodedContractFunction(randomBytes(4), false, randomBytes(64));
}
}

Expand Down

0 comments on commit 45c1f51

Please sign in to comment.