Skip to content
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
204 changes: 46 additions & 158 deletions noir-projects/aztec-nr/aztec/src/messages/processing/offchain.nr

Large diffs are not rendered by default.

98 changes: 93 additions & 5 deletions yarn-project/aztec.js/src/contract/batch_call.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import { Fr } from '@aztec/foundation/curves/bn254';
import { FunctionCall, FunctionSelector, FunctionType } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { ExecutionPayload, TxSimulationResult, UtilityExecutionResult } from '@aztec/stdlib/tx';
import {
ExecutionPayload,
OFFCHAIN_MESSAGE_IDENTIFIER,
type OffchainEffect,
TxSimulationResult,
UtilityExecutionResult,
} from '@aztec/stdlib/tx';

import { type MockProxy, mock } from 'jest-mock-extended';

import type { Wallet } from '../wallet/wallet.js';
import { BatchCall } from './batch_call.js';

function mockTxSimResult(overrides: { anchorBlockTimestamp?: bigint; offchainEffects?: OffchainEffect[] } = {}) {
const txSimResult = mock<TxSimulationResult>();
Object.defineProperty(txSimResult, 'offchainEffects', { value: overrides.offchainEffects ?? [] });
Object.defineProperty(txSimResult, 'publicInputs', {
value: {
constants: {
anchorBlockHeader: { globalVariables: { timestamp: overrides.anchorBlockTimestamp ?? 0n } },
},
},
});
return txSimResult;
}

function createUtilityExecutionPayload(
functionName: string,
args: Fr[],
Expand Down Expand Up @@ -114,12 +133,11 @@ describe('BatchCall', () => {
const privateReturnValues = [Fr.random(), Fr.random()];
const publicReturnValues = [Fr.random()];

const txSimResult = mock<TxSimulationResult>();
const txSimResult = mockTxSimResult();
txSimResult.getPrivateReturnValues.mockReturnValue({
nested: [{ values: privateReturnValues }],
} as any);
txSimResult.getPublicReturnValues.mockReturnValue([{ values: publicReturnValues }] as any);
Object.defineProperty(txSimResult, 'offchainEffects', { value: [] });

// Mock wallet.batch to return both utility results and simulateTx result
wallet.batch.mockResolvedValue([
Expand Down Expand Up @@ -220,6 +238,77 @@ describe('BatchCall', () => {
expect(results[1].result).toEqual(utilityResult2.result[0].toBigInt());
});

it('should include empty offchainEffects and offchainMessages in utility call results', async () => {
const contractAddress = await AztecAddress.random();
const utilityPayload = createUtilityExecutionPayload('view', [], contractAddress);

batchCall = new BatchCall(wallet, [utilityPayload]);

const utilityResult = UtilityExecutionResult.random();
wallet.batch.mockResolvedValue([{ name: 'executeUtility', result: utilityResult }] as any);

const results = await batchCall.simulate({ from: await AztecAddress.random() });

expect(results).toHaveLength(1);
expect(results[0].offchainEffects).toEqual([]);
expect(results[0].offchainMessages).toEqual([]);
});

// This is not a great test, mostly because it is very synthetic, mocking too much stuff around. I wanted something
// that exercised the offchain effects processing side of things in the case of batches, and this is more or less
// what matches how things are done in this suite, but the fact that we need to resort to mocking so much seems
// like a smell. We should revisit when we have more time to rethink the suite.
it('should extract offchain messages with anchor block timestamp from mixed batch', async () => {
const contractAddress = await AztecAddress.random();
const recipient = await AztecAddress.random();
const msgPayload = [Fr.random(), Fr.random()];
const anchorBlockTimestamp = 1234567890n;

batchCall = new BatchCall(wallet, [
createUtilityExecutionPayload('getBalance', [Fr.random()], contractAddress),
createPrivateExecutionPayload('transfer', [Fr.random()], contractAddress, 1),
createPrivateExecutionPayload('transfer', [Fr.random()], contractAddress, 1),
]);

const offchainEffects: OffchainEffect[] = [
{
data: [OFFCHAIN_MESSAGE_IDENTIFIER, recipient.toField(), ...msgPayload],
contractAddress: contractAddress,
},
];

const txSimResult = mockTxSimResult({ anchorBlockTimestamp, offchainEffects });
txSimResult.getPrivateReturnValues.mockReturnValue({
nested: [{ values: [Fr.random()] }, { values: [Fr.random()] }],
} as any);

const utilityResult = UtilityExecutionResult.random();
wallet.batch.mockResolvedValue([
{ name: 'executeUtility', result: utilityResult },
{ name: 'simulateTx', result: txSimResult },
] as any);

const results = await batchCall.simulate({ from: await AztecAddress.random() });

expect(results).toHaveLength(3);

// Utility result has empty offchain output
expect(results[0].offchainMessages).toEqual([]);
expect(results[0].offchainEffects).toEqual([]);

// Private results carry the offchain messages from the tx simulation
for (const idx of [1, 2]) {
expect(results[idx].offchainMessages).toHaveLength(1);
expect(results[idx].offchainMessages[0]).toEqual({
recipient,
payload: msgPayload,
contractAddress: contractAddress,
anchorBlockTimestamp,
});
expect(results[idx].offchainEffects).toEqual([]);
}
});

it('should handle only private/public calls using wallet.batch with simulateTx', async () => {
const contractAddress1 = await AztecAddress.random();
const contractAddress2 = await AztecAddress.random();
Expand All @@ -232,12 +321,11 @@ describe('BatchCall', () => {
const privateReturnValues = [Fr.random()];
const publicReturnValues = [Fr.random()];

const txSimResult = mock<TxSimulationResult>();
const txSimResult = mockTxSimResult();
txSimResult.getPrivateReturnValues.mockReturnValue({
nested: [{ values: privateReturnValues }],
} as any);
txSimResult.getPublicReturnValues.mockReturnValue([{ values: publicReturnValues }] as any);
Object.defineProperty(txSimResult, 'offchainEffects', { value: [] });

wallet.batch.mockResolvedValue([{ name: 'simulateTx', result: txSimResult }] as any);

Expand Down
5 changes: 4 additions & 1 deletion yarn-project/aztec.js/src/contract/batch_call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ export class BatchCall extends BaseContractInteraction {

results[callIndex] = {
result: rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : [],
...extractOffchainOutput(simulatedTx.offchainEffects, simulatedTx.publicInputs.expirationTimestamp),
...extractOffchainOutput(
simulatedTx.offchainEffects,
simulatedTx.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp,
),
};
});
}
Expand Down
36 changes: 36 additions & 0 deletions yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
TxSimulationResult,
UtilityExecutionResult,
} from '@aztec/stdlib/tx';
import { OFFCHAIN_MESSAGE_IDENTIFIER } from '@aztec/stdlib/tx';

import { type MockProxy, mock } from 'jest-mock-extended';

Expand Down Expand Up @@ -228,6 +229,41 @@ describe('Contract Class', () => {
expect(result).toBe(42n);
});

it('should extract offchain messages with anchor block timestamp on simulate', async () => {
const recipient = await AztecAddress.random();
const msgPayload = [Fr.random(), Fr.random()];
const anchorBlockTimestamp = 9999n;

const txSimResult = mock<TxSimulationResult>();
txSimResult.getPrivateReturnValues.mockReturnValue({ nested: [{ values: [] }] } as any);
Object.defineProperty(txSimResult, 'offchainEffects', {
value: [
{
data: [OFFCHAIN_MESSAGE_IDENTIFIER, recipient.toField(), ...msgPayload],
contractAddress,
},
],
});
Object.defineProperty(txSimResult, 'publicInputs', {
value: {
constants: { anchorBlockHeader: { globalVariables: { timestamp: anchorBlockTimestamp } } },
},
});

wallet.simulateTx.mockResolvedValue(txSimResult);

const fooContract = Contract.at(contractAddress, defaultArtifact, wallet);
const result = await fooContract.methods.bar(1, 2).simulate({ from: account.getAddress() });

expect(result.offchainMessages).toHaveLength(1);
expect(result.offchainMessages[0]).toEqual({
recipient,
payload: msgPayload,
contractAddress,
anchorBlockTimestamp,
});
});

it('allows nullish values for Option parameters', () => {
const fooContract = Contract.at(contractAddress, defaultArtifact, wallet);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction {
const returnValue = rawReturnValues ? decodeFromAbi(this.functionDao.returnTypes, rawReturnValues) : [];
const offchainOutput = extractOffchainOutput(
simulatedTx.offchainEffects,
simulatedTx.publicInputs.expirationTimestamp,
simulatedTx.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp,
);

if (options.includeMetadata || options.fee?.estimateGas) {
Expand Down
106 changes: 106 additions & 0 deletions yarn-project/aztec.js/src/contract/deploy_method.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Fr } from '@aztec/foundation/curves/bn254';
import { type ContractArtifact, FunctionType } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
import { Gas } from '@aztec/stdlib/gas';
import { PublicKeys } from '@aztec/stdlib/keys';
import { OFFCHAIN_MESSAGE_IDENTIFIER, type OffchainEffect, type TxSimulationResult } from '@aztec/stdlib/tx';

import { type MockProxy, mock } from 'jest-mock-extended';

import type { Wallet } from '../wallet/wallet.js';
import type { ContractBase } from './contract_base.js';
import { DeployMethod } from './deploy_method.js';

describe('DeployMethod', () => {
let wallet: MockProxy<Wallet>;

const artifact: ContractArtifact = {
name: 'TestContract',
functions: [
{
name: 'constructor',
isInitializer: true,
functionType: FunctionType.PRIVATE,
isOnlySelf: false,
isStatic: false,
debugSymbols: '',
parameters: [],
returnTypes: [],
errorTypes: {},
bytecode: Buffer.alloc(8, 0xfa),
verificationKey: Buffer.alloc(4064).toString('base64'),
},
{
name: 'public_dispatch',
isInitializer: false,
isStatic: false,
functionType: FunctionType.PUBLIC,
isOnlySelf: false,
parameters: [{ name: 'selector', type: { kind: 'field' }, visibility: 'public' }],
returnTypes: [],
errorTypes: {},
bytecode: Buffer.alloc(8, 0xfb),
debugSymbols: '',
},
],
nonDispatchPublicFunctions: [],
outputs: { structs: {}, globals: {} },
fileMap: {},
storageLayout: {},
};

beforeEach(() => {
wallet = mock<Wallet>();
wallet.registerContract.mockResolvedValue({} as ContractInstanceWithAddress);
wallet.getContractClassMetadata.mockResolvedValue({ isContractClassPubliclyRegistered: true } as any);
wallet.getContractMetadata.mockResolvedValue({ isContractPubliclyDeployed: true } as any);
});

it('should extract offchain messages with anchor block timestamp on simulate', async () => {
const recipient = await AztecAddress.random();
const contractAddress = await AztecAddress.random();
const msgPayload = [Fr.random(), Fr.random()];
const anchorBlockTimestamp = 42000n;

const offchainEffects: OffchainEffect[] = [
{
data: [OFFCHAIN_MESSAGE_IDENTIFIER, recipient.toField(), ...msgPayload],
contractAddress,
},
];

const txSimResult = mock<TxSimulationResult>();
Object.defineProperty(txSimResult, 'offchainEffects', { value: offchainEffects });
Object.defineProperty(txSimResult, 'publicInputs', {
value: {
constants: { anchorBlockHeader: { globalVariables: { timestamp: anchorBlockTimestamp } } },
},
});
Object.defineProperty(txSimResult, 'stats', { value: {} });
Object.defineProperty(txSimResult, 'gasUsed', {
value: { totalGas: Gas.empty(), teardownGas: Gas.empty() },
});

wallet.simulateTx.mockResolvedValue(txSimResult);

const deployMethod = new DeployMethod(
PublicKeys.default(),
wallet,
artifact,
(instance, w) => ({ instance, wallet: w }) as unknown as ContractBase,
[],
);

const result = await deployMethod.simulate({ from: await AztecAddress.random() });

expect(result.offchainMessages).toHaveLength(1);
expect(result.offchainMessages[0]).toEqual({
recipient,
payload: msgPayload,
contractAddress,
anchorBlockTimestamp,
});
expect(result.offchainEffects).toEqual([]);
});
});
5 changes: 4 additions & 1 deletion yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,10 @@ export class DeployMethod<TContract extends ContractBase = ContractBase> extends
);
return {
stats: simulatedTx.stats!,
...extractOffchainOutput(simulatedTx.offchainEffects, simulatedTx.publicInputs.expirationTimestamp),
...extractOffchainOutput(
simulatedTx.offchainEffects,
simulatedTx.publicInputs.constants.anchorBlockHeader.globalVariables.timestamp,
),
result: undefined,
estimatedGas: { gasLimits, teardownGasLimits },
};
Expand Down
Loading
Loading