Skip to content

Commit 2e114da

Browse files
authored
test: AVM opcode spammer (#18919)
# AVM Opcode Spammer ## Overview The **Opcode Spammer** is a bytecode-building framework for spamming AVM opcodes. It generates bytecode that repeatedly executes target opcodes until the transaction runs out of gas, allowing us to measure worst-case simulation and proving times, and confirm that all such transactions are simulatable and provable. ## What's left after this 1. Spammer tests for external (regular/static) calls/returns/reverts, internalcalls, min/max scaling factors for dynamic gas opcodes, all wire formats (currently only doing smallest wire formats) 2. Scripts to run proving benchmarks with dedicated resources (and maybe only a subset of the cases) 4. Use random numbers (preferably seeded) instead of consts like 42 5. Consider using calldatacopy for "unique" values (for EMITNULLIFIER and SSTORE) (one big cdc instead of 1 add per target instr) 7. Make the tests for the following more meaningful: - CALLDATACOPY - RETURNDATACOPY - RETURNDATASIZE - GETENVVAR - GETCONTRACTINSTANCE ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ SPAM_CONFIGS │ │ Record<Opcode, SpamConfig[]> │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ ADD_8 │ │ POSEIDON2 │ │EMITNULLIFIER│ ... │ │ │ [7 configs] │ │ [1 config] │ │ [1 config] │ │ │ │ (per type) │ │ │ │ (limit=63) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ getSpamConfigsPerOpcode() │ │ Returns { opcodes, config[] } for test iteration │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ testOpcodeSpamCase() │ │ Routes to appropriate bytecode generator & executes test │ │ │ │ config.limit === undefined? │ │ YES → testStandardOpcodeSpam() │ │ NO → testSideEffectOpcodeSpam() │ └─────────────────────────────────────────────────────────────────┘ ``` ## Two Execution Strategies ### Strategy 1: Standard Opcodes (Gas-Limited) For opcodes without per-TX limits (arithmetic, comparisons, memory ops, etc.), we create a single contract with an infinite loop: ``` ┌────────────────────────────────────────────────────────────────┐ │ SINGLE CONTRACT │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ SETUP PHASE │ │ │ │ SET mem[0] = initial_value │ │ │ │ SET mem[1] = operand │ │ │ │ ... │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ LOOP (fills remaining bytecode space) ◄─────┐ │ │ │ │ TARGET_OPCODE ─┐ │ │ │ │ │ TARGET_OPCODE │ unrolled N times │ │ │ │ │ TARGET_OPCODE │ (N = available_bytes / instr_size)│ │ │ │ │ ... ─┘ │ │ │ │ │ JUMP back ──────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ Executes until: OUT OF GAS │ └────────────────────────────────────────────────────────────────┘ ``` **Bytecode Layout:** ``` ┌─────────────────────────────────────────────────────────────────┐ │ 0x00: SET instructions (setup) │ │ ... │ │ 0xNN: ┌─── LOOP START ◄──────────────────────────────────────┐ │ │ │ TARGET_OPCODE │ │ │ │ TARGET_OPCODE (unrolled to fill max bytecode size) │ │ │ │ TARGET_OPCODE │ │ │ │ ... │ │ │ └─► JUMP 0xNN ─────────────────────────────────────────┘ │ │ MAX_BYTECODE_BYTES │ └─────────────────────────────────────────────────────────────────┘ ``` ### Strategy 2: Side-Effect Limited Opcodes (Nested Call Pattern) For opcodes with per-TX limits (EMITNOTEHASH, EMITNULLIFIER, SENDL2TOL1MSG, etc.), we use a two-contract pattern where the inner contract executes side effects up to the limit, then REVERTs to discard them: ``` ┌─────────────────────────────────────────────────────────────────┐ │ OUTER CONTRACT │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ SETUP │ │ │ │ CALLDATACOPY inner_address from calldata[0] │ │ │ │ SET l2Gas = MAX_UINT32 │ │ │ │ SET daGas = MAX_UINT32 │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ LOOP ◄────┐ │ │ │ │ CALL inner_contract ──────────────────────┐ │ │ │ │ │ JUMP back ─────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ Executes until: OUT OF GAS │ │ └───────────────────────────────────────────────│─────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ INNER CONTRACT │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ SETUP │ │ │ │ SET initial values for side-effect opcode │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ BODY (unrolled, NOT a loop) │ │ │ │ SIDE_EFFECT_OPCODE ─┐ │ │ │ │ SIDE_EFFECT_OPCODE │ repeated `limit` times │ │ │ │ SIDE_EFFECT_OPCODE │ (e.g., 64 for EMITNOTEHASH) │ │ │ │ ... ─┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ CLEANUP │ │ │ │ REVERT (discards all side effects from this call) │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **Why this pattern?** Side-effect opcodes have per-TX limits: - `EMITNOTEHASH`: max 64 per TX - `EMITNULLIFIER`: max 63 per TX (one reserved for TX nullifier) - `SENDL2TOL1MSG`: max 8 per TX - `EMITUNENCRYPTEDLOG`: limited by total log payload size By having the inner contract REVERT after emitting side effects, those effects are discarded, allowing the outer contract to call it again. This enables thousands of opcode executions per TX instead of just the limit. ## SpamConfig Structure ```typescript interface SpamConfig { // Memory cells to initialize before the loop setup: SetupItem[]; // Factory to create target instruction(s) to spam targetInstructions: () => Bufferable[]; // Instructions to run after target spam (for side-effect opcodes) cleanupInstructions?: () => Bufferable[]; // Per-TX limit (triggers nested call pattern if set) limit?: number; // Label for test display (e.g., "UINT32" for type variants) label?: string; } ``` ## Type Variants Many opcodes support multiple types. The spammer tests each type variant separately: ```typescript // ADD_8 has 7 configs - one per supported type [Opcode.ADD_8]: ALL_TAGS.map(tag => ({ label: TypeTag[tag], // "FIELD", "UINT1", "UINT8", etc. setup: [ { offset: 0, value: withTag(1n, tag) }, { offset: 1, value: withTag(1n, tag) }, ], targetInstructions: () => [new Add(0, 0, 1, 0).as(Opcode.ADD_8, Add.wireFormat8)], })), ``` Test output hierarchy: ``` ADD_8 ├── ADD_8/FIELD ✓ ├── ADD_8/UINT1 ✓ ├── ADD_8/UINT8 ✓ ├── ADD_8/UINT16 ✓ ├── ADD_8/UINT32 ✓ ├── ADD_8/UINT64 ✓ └── ADD_8/UINT128 ✓ ``` ### In Tests ```typescript import { getSpamConfigsPerOpcode, testOpcodeSpamCase } from '@aztec/simulator/public/fixtures'; const groupedSpamConfigs = getSpamConfigsPerOpcode(); describe.each(groupedSpamConfigs)('$opcode', ({ configs }) => { it.each(configs)('$label', async config => { await testOpcodeSpamCase(tester, config, expectToBeTrue); }); }); ``` ## Test Suites ### Simulation Benchmarks `yarn-project/simulator/src/public/public_tx_simulator/apps_tests/opcode_spam.test.ts` Runs opcode spam through the C++ simulator (and optionally TS vs C++ comparison). ### Proving Benchmarks `yarn-project/bb-prover/src/avm_proving_tests/avm_opcode_spam.test.ts` Runs opcode spam through full AVM proving. Skipped in CI (meant for local measurement).
1 parent 07bfac8 commit 2e114da

File tree

19 files changed

+1564
-54
lines changed

19 files changed

+1564
-54
lines changed
Binary file not shown.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createLogger } from '@aztec/foundation/log';
2+
import {
3+
TestExecutorMetrics,
4+
defaultGlobals,
5+
getSpamConfigsPerOpcode,
6+
testOpcodeSpamCase,
7+
} from '@aztec/simulator/public/fixtures';
8+
import { NativeWorldStateService } from '@aztec/world-state';
9+
10+
import { mkdirSync, writeFileSync } from 'fs';
11+
import path from 'path';
12+
13+
import { AvmProvingTester } from './avm_proving_tester.js';
14+
15+
// NOTE: this test is meant to be run locally for measurements or via bb-prover/bootstrap.sh.
16+
// Set RUN_AVM_OPCODE_SPAM=1 to enable.
17+
const describeOrSkip = process.env.RUN_AVM_OPCODE_SPAM ? describe : describe.skip;
18+
19+
describeOrSkip('AVM Opcode Spammer Proving Benchmarks', () => {
20+
const logger = createLogger('avm-opcode-spam-proving');
21+
22+
// Get test cases from the spammer config (grouped by opcode)
23+
// Limit to 2 configs per opcode otherwise this suite will take hours
24+
const groupedSpamConfigs = getSpamConfigsPerOpcode(/*maxConfigsPerOpcode=*/ 2);
25+
26+
// Shared metrics instance for benchmark collection
27+
const metrics = new TestExecutorMetrics();
28+
// Full proving only (no check-circuit mode)
29+
let worldStateService: NativeWorldStateService;
30+
let tester: AvmProvingTester;
31+
32+
afterAll(() => {
33+
if (process.env.BENCH_OUTPUT) {
34+
mkdirSync(path.dirname(process.env.BENCH_OUTPUT), { recursive: true });
35+
writeFileSync(process.env.BENCH_OUTPUT, metrics.toGithubActionBenchmarkJSON());
36+
} else if (process.env.BENCH_OUTPUT_MD) {
37+
writeFileSync(process.env.BENCH_OUTPUT_MD, metrics.toPrettyString());
38+
} else {
39+
logger.info(`\n`);
40+
logger.info(metrics.toPrettyString());
41+
}
42+
});
43+
44+
beforeEach(async () => {
45+
worldStateService = await NativeWorldStateService.tmp();
46+
// FULL PROVING! Not check-circuit.
47+
tester = await AvmProvingTester.new(
48+
worldStateService,
49+
/*checkCircuitOnly=*/ false,
50+
/*globals=*/ defaultGlobals(),
51+
metrics,
52+
);
53+
tester.setMetricsPrefix(`FullProving Opcode Spam`);
54+
});
55+
56+
afterEach(async () => {
57+
await worldStateService.close();
58+
});
59+
60+
describe.each(groupedSpamConfigs)('$opcode', ({ configs }) => {
61+
it.each(configs)(
62+
'$label',
63+
async config => {
64+
await testOpcodeSpamCase(tester, config);
65+
},
66+
600_000,
67+
);
68+
});
69+
});

yarn-project/ivc-integration/src/avm_integration.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createLogger } from '@aztec/foundation/log';
55
import { mapAvmCircuitPublicInputsToNoir } from '@aztec/noir-protocol-circuits-types/server';
66
import { AvmTestContractArtifact } from '@aztec/noir-test-contracts.js/AvmTest';
77
import { PublicTxSimulationTester, bulkTest, executeAvmMinimalPublicTx } from '@aztec/simulator/public/fixtures';
8-
import { AvmCircuitInputs } from '@aztec/stdlib/avm';
8+
import { AvmCircuitInputs, PublicSimulatorConfig } from '@aztec/stdlib/avm';
99
import { RecursiveProof } from '@aztec/stdlib/proofs';
1010
import { VerificationKeyAsFields } from '@aztec/stdlib/vks';
1111
import { NativeWorldStateService } from '@aztec/world-state/native';
@@ -34,6 +34,15 @@ jest.setTimeout(120_000);
3434

3535
const logger = createLogger('ivc-integration:test:avm-integration');
3636

37+
const simConfig: PublicSimulatorConfig = PublicSimulatorConfig.from({
38+
skipFeeEnforcement: false,
39+
collectCallMetadata: true, // For results.
40+
collectDebugLogs: false,
41+
collectHints: true, // Required for proving!
42+
collectPublicInputs: true, // Required for proving!
43+
collectStatistics: false,
44+
});
45+
3746
async function proveMockPublicBaseRollup(
3847
avmCircuitInputs: AvmCircuitInputs,
3948
bbWorkingDirectory: string,
@@ -107,7 +116,13 @@ describe('AVM Integration', () => {
107116
bbWorkingDirectory = await getWorkingDirectory('bb-avm-integration-');
108117

109118
worldStateService = await NativeWorldStateService.tmp();
110-
simTester = await PublicTxSimulationTester.create(worldStateService);
119+
simTester = await PublicTxSimulationTester.create(
120+
worldStateService,
121+
/*globals=*/ undefined, // default
122+
/*metrics=*/ undefined,
123+
/*useCppSimulator=*/ true,
124+
simConfig,
125+
);
111126
});
112127

113128
afterEach(async () => {

yarn-project/ivc-integration/src/rollup_ivc_integration.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createLogger } from '@aztec/foundation/log';
55
import { mapAvmCircuitPublicInputsToNoir } from '@aztec/noir-protocol-circuits-types/server';
66
import { AvmTestContractArtifact } from '@aztec/noir-test-contracts.js/AvmTest';
77
import { PublicTxSimulationTester, bulkTest } from '@aztec/simulator/public/fixtures';
8-
import { AvmCircuitInputs, AvmCircuitPublicInputs } from '@aztec/stdlib/avm';
8+
import { AvmCircuitInputs, AvmCircuitPublicInputs, PublicSimulatorConfig } from '@aztec/stdlib/avm';
99
import { RecursiveProof } from '@aztec/stdlib/proofs';
1010
import { VerificationKeyAsFields } from '@aztec/stdlib/vks';
1111
import { NativeWorldStateService } from '@aztec/world-state/native';
@@ -38,6 +38,15 @@ jest.setTimeout(150_000);
3838

3939
const logger = createLogger('ivc-integration:test:rollup-native');
4040

41+
const simConfig: PublicSimulatorConfig = PublicSimulatorConfig.from({
42+
skipFeeEnforcement: false,
43+
collectCallMetadata: true, // For results.
44+
collectDebugLogs: false,
45+
collectHints: true, // Required for proving!
46+
collectPublicInputs: true, // Required for proving!
47+
collectStatistics: false,
48+
});
49+
4150
describe('Rollup IVC Integration', () => {
4251
let bbBinaryPath: string;
4352

@@ -72,7 +81,13 @@ describe('Rollup IVC Integration', () => {
7281
const avmWorkingDirectory = await getWorkingDirectory('bb-rollup-ivc-integration-avm-');
7382

7483
const worldStateService = await NativeWorldStateService.tmp();
75-
const simTester = await PublicTxSimulationTester.create(worldStateService);
84+
const simTester = await PublicTxSimulationTester.create(
85+
worldStateService,
86+
/*globals=*/ undefined, // default
87+
/*metrics=*/ undefined,
88+
/*useCppSimulator=*/ true,
89+
simConfig,
90+
);
7691
const avmSimulationResult = await bulkTest(simTester, logger, AvmTestContractArtifact);
7792
await worldStateService.close();
7893
expect(avmSimulationResult.revertCode.isOK()).toBe(true);

yarn-project/simulator/artifacts/avm_minimal_inputs.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"gasFees": { "feePerDaGas": "2", "feePerL2Gas": "3" }
1212
},
1313
"tx": {
14-
"hash": "0x02440a894870f8078ce6065f76c2f598e2f3df9719f1a78038057c20eb096516",
14+
"hash": "0x0fe93625c73eb2fd8131834fac90f24341cc1b87bdfa9c09f7f61c7f5f943bc5",
1515
"gasSettings": {
1616
"gasLimits": { "daGas": 12582912, "l2Gas": 6000000 },
1717
"teardownGasLimits": { "daGas": 0, "l2Gas": 0 },
@@ -34,9 +34,9 @@
3434
"msgSender": "0x000000000000000000000000000000000000000000000000000000000000002a",
3535
"contractAddress": "0x03deefb26b3b88ef515e0ce5bd1402ea3a7c12569597cddb19ce6260444d625f",
3636
"isStaticCall": false,
37-
"calldataHash": "0x210b9efa10b8e4522089aa9b227e18fb47dbf4c9a53b37225416d7900600be65"
37+
"calldataHash": "0x0d11f0bac132909f8a77a45be32a0dfad89702d957f3f1963b790545be3f966c"
3838
},
39-
"calldata": ["0x000000000000000000000000000000000000000000000000000000000d6ab3ae"]
39+
"calldata": []
4040
}
4141
],
4242
"teardownEnqueuedCall": null,
@@ -856,7 +856,7 @@
856856
"msgSender": "0x000000000000000000000000000000000000000000000000000000000000002a",
857857
"contractAddress": "0x03deefb26b3b88ef515e0ce5bd1402ea3a7c12569597cddb19ce6260444d625f",
858858
"isStaticCall": false,
859-
"calldataHash": "0x210b9efa10b8e4522089aa9b227e18fb47dbf4c9a53b37225416d7900600be65"
859+
"calldataHash": "0x0d11f0bac132909f8a77a45be32a0dfad89702d957f3f1963b790545be3f966c"
860860
},
861861
{
862862
"msgSender": "0x0000000000000000000000000000000000000000000000000000000000000000",

yarn-project/simulator/src/public/avm/avm_memory_types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ const TAG_FOR_MEM_VAL = new Map<string, TypeTag>([
416416
['Uint128', TypeTag.UINT128],
417417
]);
418418

419-
const VALID_TAGS = new Set([
419+
export const VALID_TAGS = new Set([
420420
TypeTag.FIELD,
421421
TypeTag.UINT1,
422422
TypeTag.UINT8,
@@ -426,7 +426,7 @@ const VALID_TAGS = new Set([
426426
TypeTag.UINT128,
427427
]);
428428

429-
const INTEGRAL_TAGS = new Set([
429+
export const INTEGRAL_TAGS = new Set([
430430
TypeTag.UINT1,
431431
TypeTag.UINT8,
432432
TypeTag.UINT16,

yarn-project/simulator/src/public/debug_fn_name.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ export async function getPublicFunctionDebugName(
1313
if (!calldata[0]) {
1414
return `<calldata[0] undefined> (Contract Address: ${contractAddress})`;
1515
}
16-
const selector = FunctionSelector.fromField(calldata[0]);
17-
return (await db.getDebugFunctionName(contractAddress, selector)) ?? selector.toString();
16+
const fallbackName = `<calldata[0]:${calldata[0].toString()}> (Contract Address: ${contractAddress})`;
17+
const selector = FunctionSelector.fromFieldOrUndefined(calldata[0]);
18+
if (!selector) {
19+
return fallbackName;
20+
}
21+
return (await db.getDebugFunctionName(contractAddress, selector)) ?? fallbackName;
1822
}
1923

2024
/**
@@ -34,7 +38,10 @@ export async function getPublicFunctionSelectorAndName(
3438
if (!calldata[0]) {
3539
return {};
3640
}
37-
const selector = FunctionSelector.fromField(calldata[0]);
41+
const selector = FunctionSelector.fromFieldOrUndefined(calldata[0]);
42+
if (!selector) {
43+
return {};
44+
}
3845
const debugName = await db.getDebugFunctionName(contractAddress, selector);
3946
return {
4047
functionSelector: selector,
Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,83 @@
11
import { FunctionType, emptyContractArtifact, emptyFunctionArtifact } from '@aztec/stdlib/abi';
22
import type { PublicTxResult } from '@aztec/stdlib/avm';
33
import { AztecAddress } from '@aztec/stdlib/aztec-address';
4+
import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
45

56
import { PublicTxSimulationTester } from './public_tx_simulation_tester.js';
67

78
/**
8-
*
9-
* Test custom bytecode (simulation or proving) with the provided bytecode.
9+
* Deploy a contract with the provided bytecode.
1010
* @param bytecode - The bytecode buffer to use
11-
* @param tester - The tester to use (simulation or proving)
12-
* @param txLabel - The label of the transaction
13-
* @param contractName - The name of the contract (default: 'CustomBytecodeContract')
11+
* @param tester - The tester to use
12+
* @param contractName - The name of the contract
13+
* @param deployer - The deployer address
14+
* @returns The deployed contract instance
1415
*/
15-
export async function testCustomBytecode(
16+
export async function deployCustomBytecode(
1617
bytecode: Buffer,
1718
tester: PublicTxSimulationTester,
18-
txLabel: string,
1919
contractName: string = 'CustomBytecodeContract',
20-
): Promise<PublicTxResult> {
21-
const deployer = AztecAddress.fromNumber(42);
22-
20+
deployer: AztecAddress = AztecAddress.fromNumber(42),
21+
): Promise<ContractInstanceWithAddress> {
2322
const contractArtifact = emptyContractArtifact();
2423
contractArtifact.name = contractName;
2524
contractArtifact.functions = [emptyFunctionArtifact()];
25+
// We use name 'public_dispatch' since that is what is expected
26+
// in a ContractArtifact. But function selectors are not required
27+
// when executing since the custom bytecode likely has no dispatch.
2628
contractArtifact.functions[0].name = 'public_dispatch';
2729
contractArtifact.functions[0].functionType = FunctionType.PUBLIC;
2830
contractArtifact.functions[0].bytecode = bytecode;
2931

30-
const testContract = await tester.registerAndDeployContract(
32+
// return the contract instance
33+
return await tester.registerAndDeployContract(
3134
/*constructorArgs=*/ [],
3235
deployer,
3336
/*contractArtifact=*/ contractArtifact,
3437
);
38+
}
3539

40+
/**
41+
* Execute a custom bytecode contract.
42+
* @param contract - The contract instance to execute
43+
* @param tester - The tester to use
44+
* @param txLabel - The label of the transaction
45+
* @param calldata - The calldata to use
46+
* @returns The execution result
47+
*/
48+
export async function executeCustomBytecode(
49+
contract: ContractInstanceWithAddress,
50+
tester: PublicTxSimulationTester,
51+
txLabel: string = 'CustomBytecodeTest',
52+
calldata: any[] = [],
53+
): Promise<PublicTxResult> {
3654
// EXECUTE! This means that if using AvmProvingTester subclass, it will PROVE the transaction!
3755
return await tester.executeTxWithLabel(
3856
/*txLabel=*/ txLabel,
39-
/*sender=*/ deployer,
57+
/*sender=*/ contract.deployer,
4058
/*setupCalls=*/ [],
41-
/*appCalls=*/ [
42-
{
43-
address: testContract.address,
44-
fnName: 'public_dispatch',
45-
args: [],
46-
},
47-
],
59+
/*appCalls=*/ [{ address: contract.address, args: calldata }],
4860
);
4961
}
62+
63+
/**
64+
* Deploy and execute a custom bytecode contract.
65+
* @param bytecode - The bytecode buffer to use
66+
* @param tester - The tester to use
67+
* @param txLabel - The label of the transaction
68+
* @param contractName - The name of the contract
69+
* @param deployer - The deployer address
70+
* @param calldata - The calldata to use
71+
* @returns The execution result
72+
*/
73+
export async function deployAndExecuteCustomBytecode(
74+
bytecode: Buffer,
75+
tester: PublicTxSimulationTester,
76+
txLabel: string = 'CustomBytecodeTest',
77+
contractName: string = 'CustomBytecodeContract',
78+
deployer: AztecAddress = AztecAddress.fromNumber(42),
79+
calldata: any[] = [],
80+
): Promise<PublicTxResult> {
81+
const testContract = await deployCustomBytecode(bytecode, tester, contractName, deployer);
82+
return await executeCustomBytecode(testContract, tester, txLabel, calldata);
83+
}

0 commit comments

Comments
 (0)