From 6ef0895ed9788c533b0caf2d2c30839552dabbcc Mon Sep 17 00:00:00 2001 From: David Banks <47112877+dbanks12@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:23:48 -0400 Subject: [PATCH] feat: new per-enqueued-call gas limit (#9033) It is not trivial to implement "startup conditions" in the AVM circuit that are not standard part of any execution. So, for now we think it'd be much easier to enforce our "max gas" in the kernel. The simulator and witgen will check the startup gas and error if its too much, but the AVM circuit will not include constraints for this. Note that this is an important constraint (whether in the kernel or AVM) because for now this gas limit also serves to ensure that the AVM circuit's trace never fills up. --- .../vm/avm/tests/execution.test.cpp | 25 ++++++++++++++++--- .../src/barretenberg/vm/avm/trace/helper.hpp | 15 +++++++++++ .../src/barretenberg/vm/aztec_constants.hpp | 1 + .../src/core/libraries/ConstantsGen.sol | 1 + .../enqueued_call_data_validator.nr | 16 ++++++++---- .../crates/types/src/constants.nr | 1 + yarn-project/circuits.js/src/constants.gen.ts | 1 + .../circuits.js/src/scripts/constants.in.ts | 1 + yarn-project/circuits.js/src/structs/gas.ts | 3 ++- .../simulator/src/avm/avm_simulator.ts | 5 ++++ .../simulator/src/avm/fixtures/index.ts | 4 +-- .../src/public/enqueued_call_simulator.ts | 11 ++++++-- .../simulator/src/public/execution.ts | 6 ++--- yarn-project/simulator/src/public/executor.ts | 10 ++++---- .../src/public/public_processor.test.ts | 21 ++++++++++------ 15 files changed, 92 insertions(+), 29 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp index 9e43b804768..d78d5efa1f0 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/tests/execution.test.cpp @@ -1760,11 +1760,30 @@ TEST_F(AvmExecutionTests, daGasLeft) validate_trace(std::move(trace), public_inputs); } +TEST_F(AvmExecutionTests, ExecutorThrowsWithTooMuchGasAllocated) +{ + std::string bytecode_hex = to_hex(OpCode::GETENVVAR_16) + // opcode GETENVVAR_16(sender) + "00" // Indirect flag + + to_hex(static_cast(EnvironmentVariable::SENDER)) + "0007"; // addr 7 + + std::vector calldata = {}; + std::vector returndata = {}; + std::vector public_inputs_vec(PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, 0); + public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET] = MAX_L2_GAS_PER_ENQUEUED_CALL + 1; + + auto bytecode = hex_to_bytes(bytecode_hex); + auto instructions = Deserialization::parse(bytecode); + + EXPECT_THROW_WITH_MESSAGE( + Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec), + "Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for execution of an enqueued call"); +} + // Should throw whenever the wrong number of public inputs are provided TEST_F(AvmExecutionTests, ExecutorThrowsWithIncorrectNumberOfPublicInputs) { - std::string bytecode_hex = to_hex(OpCode::GETENVVAR_16) + // opcode SENDER - "00" // Indirect flag + std::string bytecode_hex = to_hex(OpCode::GETENVVAR_16) + // opcode GETENVVAR_16(sender) + "00" // Indirect flag + to_hex(static_cast(EnvironmentVariable::SENDER)) + "0007"; // addr 7 std::vector calldata = {}; @@ -1774,7 +1793,7 @@ TEST_F(AvmExecutionTests, ExecutorThrowsWithIncorrectNumberOfPublicInputs) auto bytecode = hex_to_bytes(bytecode_hex); auto instructions = Deserialization::parse(bytecode); - EXPECT_THROW_WITH_MESSAGE(Execution::gen_trace(instructions, calldata, returndata, public_inputs_vec), + EXPECT_THROW_WITH_MESSAGE(Execution::gen_trace(instructions, returndata, calldata, public_inputs_vec), "Public inputs vector is not of PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH"); } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.hpp index 946feea07c1..a3051d91235 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/helper.hpp @@ -37,6 +37,21 @@ template VmPublicInputs convert_public_inputs(std::vector) { + if (public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET] > MAX_L2_GAS_PER_ENQUEUED_CALL) { + throw_or_abort( + "Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for execution of an enqueued call"); + } + } else { + if (public_inputs_vec[L2_START_GAS_LEFT_PCPI_OFFSET].get_value() > MAX_L2_GAS_PER_ENQUEUED_CALL) { + throw_or_abort( + "Cannot allocate more than MAX_L2_GAS_PER_ENQUEUED_CALL to the AVM for execution of an enqueued call"); + } + } + std::array& kernel_inputs = std::get(public_inputs); // Copy items from PublicCircuitPublicInputs vector to public input columns diff --git a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp index 3f6c828a6e6..2b923e6a713 100644 --- a/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/aztec_constants.hpp @@ -12,6 +12,7 @@ #define MAX_NULLIFIER_NON_EXISTENT_READ_REQUESTS_PER_CALL 16 #define MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_CALL 16 #define MAX_UNENCRYPTED_LOGS_PER_CALL 4 +#define MAX_L2_GAS_PER_ENQUEUED_CALL 5000000 #define AZTEC_ADDRESS_LENGTH 1 #define GAS_FEES_LENGTH 2 #define GAS_LENGTH 2 diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 1a3d9eb9c1f..bbea41984b2 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -117,6 +117,7 @@ library Constants { 14061769416655647708490531650437236735160113654556896985372298487345; uint256 internal constant DEFAULT_GAS_LIMIT = 1000000000; uint256 internal constant DEFAULT_TEARDOWN_GAS_LIMIT = 100000000; + uint256 internal constant MAX_L2_GAS_PER_ENQUEUED_CALL = 5000000; uint256 internal constant DEFAULT_MAX_FEE_PER_GAS = 10; uint256 internal constant DEFAULT_INCLUSION_FEE = 0; uint256 internal constant DA_BYTES_PER_FIELD = 32; diff --git a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/enqueued_call_data_validator.nr b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/enqueued_call_data_validator.nr index 7688f780885..a66ea0e5169 100644 --- a/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/enqueued_call_data_validator.nr +++ b/noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/components/enqueued_call_data_validator.nr @@ -5,7 +5,7 @@ use dep::types::{ kernel_circuit_public_inputs::PublicKernelCircuitPublicInputs, enqueued_call_data::EnqueuedCallData, public_call_request::PublicCallRequest, validation_requests::PublicValidationRequestArrayLengths }, - utils::arrays::array_length + constants::MAX_L2_GAS_PER_ENQUEUED_CALL, utils::arrays::array_length }; pub struct EnqueuedCallDataValidator { @@ -89,17 +89,23 @@ impl EnqueuedCallDataValidator { // Validates that the start gas injected into the vm circuit matches the remaining gas. fn validate_start_gas(self, previous_kernel: PublicKernelCircuitPublicInputs) { let enqueued_call_start_gas = self.enqueued_call.data.start_gas_left; + // NOTE: the AVM circuit will fail to generate a proof if its "start gas" is > MAX_L2_GAS_PER_ENQUEUED_CALL, + // so the kernel never allocates more than that maximum to one enqueued call. if self.phase != PublicKernelPhase.TEARDOWN { // An enqueued call's start gas is the remaining gas left in the transaction after the previous kernel. let tx_gas_limits = previous_kernel.constants.tx_context.gas_settings.gas_limits; - let computed_start_gas = tx_gas_limits.sub(previous_kernel.end.gas_used).sub(previous_kernel.end_non_revertible.gas_used); + let mut computed_start_gas = tx_gas_limits.sub(previous_kernel.end.gas_used).sub(previous_kernel.end_non_revertible.gas_used); + // Keep L2 gas below max + computed_start_gas.l2_gas = std::cmp::min(computed_start_gas.l2_gas, MAX_L2_GAS_PER_ENQUEUED_CALL); assert_eq( - enqueued_call_start_gas, computed_start_gas, "Start gas for enqueued call does not match transaction gas left" + enqueued_call_start_gas, computed_start_gas, "Start gas for enqueued call does not match transaction gas left (with MAX_L2_GAS_PER_ENQUEUED_CALL applied)" ); } else { - let teardown_gas_limit = previous_kernel.constants.tx_context.gas_settings.teardown_gas_limits; + let mut teardown_gas_limit = previous_kernel.constants.tx_context.gas_settings.teardown_gas_limits; + // Keep L2 gas below max + teardown_gas_limit.l2_gas = std::cmp::min(teardown_gas_limit.l2_gas, MAX_L2_GAS_PER_ENQUEUED_CALL); assert_eq( - enqueued_call_start_gas, teardown_gas_limit, "Start gas for enqueued call does not match teardown gas allocation" + enqueued_call_start_gas, teardown_gas_limit, "Start gas for enqueued call does not match teardown gas allocation (with MAX_L2_GAS_PER_ENQUEUED_CALL applied)" ); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 58f914aabfd..aaa531ae2b3 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -168,6 +168,7 @@ global DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE = 0x85864497636cf755ae7bd // GAS DEFAULTS global DEFAULT_GAS_LIMIT: u32 = 1_000_000_000; global DEFAULT_TEARDOWN_GAS_LIMIT: u32 = 100_000_000; +global MAX_L2_GAS_PER_ENQUEUED_CALL: u32 = 5_000_000; global DEFAULT_MAX_FEE_PER_GAS: Field = 10; global DEFAULT_INCLUSION_FEE: Field = 0; global DA_BYTES_PER_FIELD: u32 = 32; diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index dbc2e1fb465..f379c07941c 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -102,6 +102,7 @@ export const DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE = 14061769416655647708490531650437236735160113654556896985372298487345n; export const DEFAULT_GAS_LIMIT = 1000000000; export const DEFAULT_TEARDOWN_GAS_LIMIT = 100000000; +export const MAX_L2_GAS_PER_ENQUEUED_CALL = 5000000; export const DEFAULT_MAX_FEE_PER_GAS = 10; export const DEFAULT_INCLUSION_FEE = 0; export const DA_BYTES_PER_FIELD = 32; diff --git a/yarn-project/circuits.js/src/scripts/constants.in.ts b/yarn-project/circuits.js/src/scripts/constants.in.ts index ce909deba97..a4783b95183 100644 --- a/yarn-project/circuits.js/src/scripts/constants.in.ts +++ b/yarn-project/circuits.js/src/scripts/constants.in.ts @@ -78,6 +78,7 @@ const CPP_CONSTANTS = [ 'MEM_TAG_U64', 'MEM_TAG_U128', 'MEM_TAG_FF', + 'MAX_L2_GAS_PER_ENQUEUED_CALL', ]; const CPP_GENERATORS: string[] = []; diff --git a/yarn-project/circuits.js/src/structs/gas.ts b/yarn-project/circuits.js/src/structs/gas.ts index e913190032b..7e1f7469492 100644 --- a/yarn-project/circuits.js/src/structs/gas.ts +++ b/yarn-project/circuits.js/src/structs/gas.ts @@ -4,6 +4,7 @@ import { type FieldsOf } from '@aztec/foundation/types'; import { inspect } from 'util'; +import { MAX_L2_GAS_PER_ENQUEUED_CALL } from '../constants.gen.js'; import { type GasFees } from './gas_fees.js'; import { type UInt32 } from './shared.js'; @@ -36,7 +37,7 @@ export class Gas { /** Returns large enough gas amounts for testing purposes. */ static test() { - return new Gas(1e9, 1e9); + return new Gas(1e9, MAX_L2_GAS_PER_ENQUEUED_CALL); } isEmpty() { diff --git a/yarn-project/simulator/src/avm/avm_simulator.ts b/yarn-project/simulator/src/avm/avm_simulator.ts index 6c036ecface..96c905cc58b 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.ts @@ -1,3 +1,4 @@ +import { MAX_L2_GAS_PER_ENQUEUED_CALL } from '@aztec/circuits.js'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { strict as assert } from 'assert'; @@ -21,6 +22,10 @@ export class AvmSimulator { private bytecode: Buffer | undefined; constructor(private context: AvmContext) { + assert( + context.machineState.gasLeft.l2Gas <= MAX_L2_GAS_PER_ENQUEUED_CALL, + `Cannot allocate more than ${MAX_L2_GAS_PER_ENQUEUED_CALL} to the AVM for execution of an enqueued call`, + ); this.log = createDebugLogger(`aztec:avm_simulator:core(f:${context.environment.functionSelector.toString()})`); } diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index 084767f0317..fb401245b66 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -1,5 +1,5 @@ import { isNoirCallStackUnresolved } from '@aztec/circuit-types'; -import { GasFees, GlobalVariables } from '@aztec/circuits.js'; +import { GasFees, GlobalVariables, MAX_L2_GAS_PER_ENQUEUED_CALL } from '@aztec/circuits.js'; import { FunctionSelector, getFunctionDebugMetadata } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -91,7 +91,7 @@ export function initGlobalVariables(overrides?: Partial): Globa */ export function initMachineState(overrides?: Partial): AvmMachineState { return AvmMachineState.fromState({ - l2GasLeft: overrides?.l2GasLeft ?? 1e8, + l2GasLeft: overrides?.l2GasLeft ?? MAX_L2_GAS_PER_ENQUEUED_CALL, daGasLeft: overrides?.daGasLeft ?? 1e8, }); } diff --git a/yarn-project/simulator/src/public/enqueued_call_simulator.ts b/yarn-project/simulator/src/public/enqueued_call_simulator.ts index cd588fbe644..a37c1b2066f 100644 --- a/yarn-project/simulator/src/public/enqueued_call_simulator.ts +++ b/yarn-project/simulator/src/public/enqueued_call_simulator.ts @@ -23,6 +23,7 @@ import { L2ToL1Message, LogHash, MAX_L1_TO_L2_MSG_READ_REQUESTS_PER_CALL, + MAX_L2_GAS_PER_ENQUEUED_CALL, MAX_L2_TO_L1_MSGS_PER_CALL, MAX_NOTE_HASHES_PER_CALL, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, @@ -120,6 +121,12 @@ export class EnqueuedCallSimulator { transactionFee: Fr, phase: PublicKernelPhase, ): Promise { + // Gas allocated to an enqueued call can be different from the available gas + // if there is more gas available than the max allocation per enqueued call. + const allocatedGas = new Gas( + /*daGas=*/ availableGas.daGas, + /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_ENQUEUED_CALL), + ); const pendingNullifiers = this.getSiloedPendingNullifiers(previousPublicKernelOutput); const startSideEffectCounter = previousPublicKernelOutput.endSideEffectCounter + 1; @@ -140,7 +147,7 @@ export class EnqueuedCallSimulator { const result = await this.publicExecutor.simulate( executionRequest, constants, - availableGas, + allocatedGas, tx.data.constants.txContext, pendingNullifiers, transactionFee, @@ -167,7 +174,7 @@ export class EnqueuedCallSimulator { accumulatedData, startSideEffectCounter, startSideEffectCounter, - availableGas, + allocatedGas, result.transactionFee, result.reverted, ); diff --git a/yarn-project/simulator/src/public/execution.ts b/yarn-project/simulator/src/public/execution.ts index 6c688c28df9..7dc41776095 100644 --- a/yarn-project/simulator/src/public/execution.ts +++ b/yarn-project/simulator/src/public/execution.ts @@ -22,8 +22,6 @@ import { } from '@aztec/circuits.js'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; -import { type Gas as AvmGas } from '../avm/avm_gas.js'; - /** * The public function execution result. */ @@ -36,9 +34,9 @@ export interface PublicExecutionResult { /** The side effect counter after executing this function call */ endSideEffectCounter: Fr; /** How much gas was available for this public execution. */ - startGasLeft: AvmGas; + startGasLeft: Gas; /** How much gas was left after this public execution. */ - endGasLeft: AvmGas; + endGasLeft: Gas; /** Transaction fee set for this tx. */ transactionFee: Fr; diff --git a/yarn-project/simulator/src/public/executor.ts b/yarn-project/simulator/src/public/executor.ts index cb4c3ec7692..d9f381f5018 100644 --- a/yarn-project/simulator/src/public/executor.ts +++ b/yarn-project/simulator/src/public/executor.ts @@ -43,7 +43,7 @@ export class PublicExecutor { * Executes a public execution request. * @param executionRequest - The execution to run. * @param constants - The constants (including global variables) to use. - * @param availableGas - The gas available at the start of this enqueued call. + * @param allocatedGas - The gas available at the start of this enqueued call. * @param txContext - Transaction context. * @param pendingSiloedNullifiers - The pending nullifier set from earlier parts of this TX. * @param transactionFee - Fee offered for this TX. @@ -55,7 +55,7 @@ export class PublicExecutor { public async simulate( executionRequest: PublicExecutionRequest, constants: CombinedConstantData, - availableGas: Gas, + allocatedGas: Gas, _txContext: TxContext, pendingSiloedNullifiers: Nullifier[], transactionFee: Fr = Fr.ZERO, @@ -85,7 +85,7 @@ export class PublicExecutor { const avmExecutionEnv = createAvmExecutionEnvironment(executionRequest, constants.globalVariables, transactionFee); - const avmMachineState = new AvmMachineState(availableGas); + const avmMachineState = new AvmMachineState(allocatedGas); const avmContext = new AvmContext(avmPersistableState, avmExecutionEnv, avmMachineState); const simulator = new AvmSimulator(avmContext); const avmResult = await simulator.execute(); @@ -111,7 +111,7 @@ export class PublicExecutor { const publicExecutionResult = trace.toPublicExecutionResult( avmExecutionEnv, - /*startGasLeft=*/ availableGas, + /*startGasLeft=*/ allocatedGas, /*endGasLeft=*/ Gas.from(avmContext.machineState.gasLeft), bytecode, avmResult, @@ -127,7 +127,7 @@ export class PublicExecutor { const _vmCircuitPublicInputs = enqueuedCallTrace.toVMCircuitPublicInputs( constants, avmExecutionEnv, - /*startGasLeft=*/ availableGas, + /*startGasLeft=*/ allocatedGas, /*endGasLeft=*/ Gas.from(avmContext.machineState.gasLeft), avmResult, ); diff --git a/yarn-project/simulator/src/public/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor.test.ts index 4e48c4166e5..60d24624aaa 100644 --- a/yarn-project/simulator/src/public/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor.test.ts @@ -724,8 +724,9 @@ describe('public_processor', () => { const revertibleRequests = tx.getRevertiblePublicExecutionRequests(); const teardownRequest = tx.getPublicTeardownExecutionRequest()!; - const gasLimits = Gas.from({ l2Gas: 1e9, daGas: 1e9 }); - const teardownGas = Gas.from({ l2Gas: 1e7, daGas: 1e7 }); + // Keep gas numbers MAX_L2_GAS_PER_ENQUEUED_CALL or the logic below has to get weird + const gasLimits = Gas.from({ l2Gas: 1e6, daGas: 1e6 }); + const teardownGas = Gas.from({ l2Gas: 1e5, daGas: 1e5 }); tx.data.constants.txContext.gasSettings = GasSettings.from({ gasLimits: gasLimits, teardownGasLimits: teardownGas, @@ -750,20 +751,26 @@ describe('public_processor', () => { let simulatorCallCount = 0; + // Keep gas numbers below MAX_L2_GAS_PER_ENQUEUED_CALL or we need + // to separately compute available start gas and "effective" start gas + // for each enqueued call after applying that max. const initialGas = gasLimits.sub(teardownGas); - const setupGasUsed = Gas.from({ l2Gas: 1e6 }); - const appGasUsed = Gas.from({ l2Gas: 2e6, daGas: 2e6 }); - const teardownGasUsed = Gas.from({ l2Gas: 3e6, daGas: 3e6 }); + const setupGasUsed = Gas.from({ l2Gas: 1e4 }); + const appGasUsed = Gas.from({ l2Gas: 2e4, daGas: 2e4 }); + const teardownGasUsed = Gas.from({ l2Gas: 3e4, daGas: 3e4 }); const afterSetupGas = initialGas.sub(setupGasUsed); const afterAppGas = afterSetupGas.sub(appGasUsed); const afterTeardownGas = teardownGas.sub(teardownGasUsed); // Total gas used is the sum of teardown gas allocation plus all expenditures along the way, // without including the gas used in the teardown phase (since that's consumed entirely up front). - const expectedTotalGasUsed = { l2Gas: 1e7 + 1e6 + 2e6, daGas: 1e7 + 2e6 }; + const expectedTotalGasUsed = teardownGas.add(setupGasUsed).add(appGasUsed); // Inclusion fee plus block gas fees times total gas used - const expectedTxFee = 1e4 + (1e7 + 1e6 + 2e6) * 1 + (1e7 + 2e6) * 1; + const expectedTxFee = + tx.data.constants.txContext.gasSettings.inclusionFee.toNumber() + + expectedTotalGasUsed.l2Gas * 1 + + expectedTotalGasUsed.daGas * 1; const transactionFee = new Fr(expectedTxFee); const simulatorResults: PublicExecutionResult[] = [