Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(oogx): CALL different scenarios #1515

Draft
wants to merge 26 commits into
base: arith-dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c7b950f
test(oogx): add draft for CALL
lorenzogentile404 Nov 19, 2024
53968fe
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Nov 29, 2024
41a0c0b
test(oogx): refine outOfGasExceptionCallTest
lorenzogentile404 Dec 1, 2024
503455c
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Dec 2, 2024
b72b906
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Dec 18, 2024
6ce6761
fix issue with short in the test
lorenzogentile404 Dec 18, 2024
b7360ae
simplify callGasCost
lorenzogentile404 Dec 18, 2024
7d95702
fix typo
lorenzogentile404 Dec 18, 2024
176cad8
push nonzero argument in case of PUSHx
lorenzogentile404 Dec 19, 2024
274ffcc
replace magic numbers with constants
lorenzogentile404 Dec 19, 2024
13cf9c0
add cold SELFDESTRUCT case
lorenzogentile404 Dec 19, 2024
5f19737
add comment
lorenzogentile404 Dec 19, 2024
b474b03
improve names of tests
lorenzogentile404 Dec 19, 2024
1bcbf5b
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Jan 9, 2025
1a13b6a
added OOGX test for SLOAD
lorenzogentile404 Jan 9, 2025
7bb8c66
completed test for OOGX for SLOAD
lorenzogentile404 Jan 9, 2025
71a1a62
generalized OOGX tests
lorenzogentile404 Jan 9, 2025
f297aac
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Jan 14, 2025
ba9cde5
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Jan 15, 2025
0cb2c89
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Jan 15, 2025
6a22ae3
managed more cases of OOGX exception
lorenzogentile404 Jan 15, 2025
5fa5de6
managed other OOGX test cases involiving a dynamic gas cost
lorenzogentile404 Jan 16, 2025
c7fae6e
added GasCostSingleton
lorenzogentile404 Jan 17, 2025
809f217
Added gasCostAccumulator in the hub for testing purpose
lorenzogentile404 Jan 17, 2025
ceaba06
Merge branch 'arith-dev' into 1514-call-exceptions-testing
lorenzogentile404 Jan 22, 2025
65635d6
fixed SSTORE OOGX test
lorenzogentile404 Jan 22, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.stream.Stream;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import net.consensys.linea.zktracer.ColumnHeader;
Expand Down Expand Up @@ -753,6 +754,9 @@ public void tracePostExecution(MessageFrame frame, Operation.OperationResult ope
}
}

// TODO: bind it to the frame so as to compute the gasCost per frame
@Getter @Setter public long gasCostAccumulator = 0;

/**
* Compares the gas costs between Linea and Besu. The total cost should be the same for both, but
* it is batched/split differently. This is especially true for opcodes requiring memory
Expand All @@ -771,6 +775,19 @@ private void compareLineaAndBesuGasCosts(
long lineaGasCostExcludingDeploymentCost =
currentSection.commonValues.gasCostExcluduingDeploymentCost();

gasCostAccumulator += besuGasCost;

System.out.println(
"Retrieved in the Hub: "
+ "opCode: "
+ opCode().name()
+ " ,besuGasCost: "
+ besuGasCost
+ " ,OOGX: "
+ (besuGasCost > frame.getRemainingGas())
+ " ,remainingGas: "
+ frame.getRemainingGas());

if (operationResult.getHaltReason() != null) {

return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ private static boolean isMemoryExpansionFault(

private static boolean isOutOfGas(MessageFrame frame, OpCode opCode, GasProjector gp) {
final long required = gp.of(frame, opCode).upfrontGasCost();
System.out.println(
"Retrieved in the Exceptions: "
+ "opCode: "
+ opCode.name()
+ " ,required: "
+ required
+ " ,OOGX: "
+ (required > frame.getRemainingGas())
+ " ,remainingGas: "
+ frame.getRemainingGas());
return required > frame.getRemainingGas();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@

package net.consensys.linea.zktracer.exceptions;

import static net.consensys.linea.testing.ToyExecutionEnvironmentV2.DEFAULT_BLOCK_NUMBER;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_CALL_VALUE;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_COLD_SLOAD;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_NEW_ACCOUNT;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_SSET;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_TRANSACTION;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_VERY_LOW;
import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_WARM_ACCESS;
import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION;
import static net.consensys.linea.zktracer.opcode.OpCodes.opCodeToOpCodeDataMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -24,33 +33,78 @@
import java.util.List;
import java.util.stream.Stream;

import com.google.common.base.Preconditions;
import net.consensys.linea.UnitTestWatcher;
import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
import net.consensys.linea.testing.ToyAccount;
import net.consensys.linea.zktracer.opcode.OpCode;
import net.consensys.linea.zktracer.opcode.OpCodeData;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

@ExtendWith(UnitTestWatcher.class)
public class OutOfGasExceptionTest {

// TODO: add tests when address is warm. Use constants such as G_WARM_ACCESS etc
@ParameterizedTest
@MethodSource("outOfGasExceptionSource")
void outOfGasExceptionColdTest(
OpCode opCode, int staticCost, int nPushes, boolean triggersOutOfGasExceptions) {
@MethodSource("outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostTestSource")
void outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostTest(
OpCode opCode, int opCodeStaticCost, int nPushes, int cornerCase) {
outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostBody(
opCode, opCodeStaticCost, nPushes, cornerCase);
}

void outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostBody(
OpCode opCode, int opCodeStaticCost, int nPushes, int cornerCase) {
BytecodeCompiler program = BytecodeCompiler.newProgram();

for (int i = 0; i < nPushes; i++) {
program.push(0);
// In order to disambiguate between empty stack items and writing a result of 0 on the stack
// we push small integers to the stack which all produce non-zero results

int pushedValue =
switch (opCode) {
case OpCode.BLOCKHASH -> Math.toIntExact(DEFAULT_BLOCK_NUMBER) - 1;
case OpCode.EXP -> i == 0 ? 5 : 2; // EXP 2 5 (2 ** 5)
default -> 7 * i + 11;
// small integer but greater than 10, so as when it represents an address
// it is not the one of a precompile contract
};
program.push(pushedValue);
}
program.op(opCode);
BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile());
bytecodeRunner.run(
(long) 21000 + nPushes * 3L + staticCost - (triggersOutOfGasExceptions ? 1 : 0));
if (triggersOutOfGasExceptions) {
int opCodeDynamicCost =
switch (opCode) {
case OpCode.SELFDESTRUCT -> GAS_CONST_G_NEW_ACCOUNT
+ GAS_CONST_G_COLD_ACCOUNT_ACCESS; // since the account is empty
case OpCode.EXP -> 50; // since the exponent requires 1 byte
case OpCode.SLOAD -> GAS_CONST_G_COLD_SLOAD; // since the slot is cold
case OpCode.BALANCE,
OpCode.EXTCODEHASH,
OpCode.EXTCODESIZE -> GAS_CONST_G_COLD_ACCOUNT_ACCESS; // since the account is cold
case OpCode.SSTORE -> GAS_CONST_G_SSET
+ GAS_CONST_G_COLD_SLOAD; // value set from zero to non-zero and slot is cold
default -> 0;
};

final long gasCost =
GAS_CONST_G_TRANSACTION
+ (long) nPushes * GAS_CONST_G_VERY_LOW
+ opCodeStaticCost
+ opCodeDynamicCost;
bytecodeRunner.run(gasCost + cornerCase);

// TODO: this is just to check if the gasCostAccumulator works
// it seems inconsistent only in the case of SSTORE at the moment
assertEquals(gasCost, GAS_CONST_G_TRANSACTION + bytecodeRunner.getHub().gasCostAccumulator());

if (cornerCase == -1) {
assertEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
Expand All @@ -61,22 +115,162 @@ void outOfGasExceptionColdTest(
}
}

static Stream<Arguments> outOfGasExceptionSource() {
static Stream<Arguments> outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostTestSource() {
List<Arguments> arguments = new ArrayList<>();
for (OpCodeData opCodeData : opCodeToOpCodeDataMap.values()) {
OpCode opCode = opCodeData.mnemonic();
int staticCost = opCodeData.stackSettings().staticGas().cost();
int delta = opCodeData.stackSettings().delta(); // number of items popped from the stack
// TODO: some opCodes are excluded for now because they may need to be treated differently
if (staticCost > 0
&& opCode != OpCode.MLOAD
&& opCode != OpCode.MSTORE8
&& opCode != OpCode.SELFDESTRUCT
&& opCode != OpCode.MSTORE) {
arguments.add(Arguments.of(opCode, staticCost, delta, true));
arguments.add(Arguments.of(opCode, staticCost, delta, false));
int opCodeStaticCost = opCodeData.stackSettings().staticGas().cost();
int nPushes = opCodeData.stackSettings().delta(); // number of items popped from the stack
if (opCode != OpCode.CALLDATACOPY // CALLDATACOPY needs the memory expansion cost
&& opCode != OpCode.CODECOPY // CODECOPY needs the memory expansion cost
&& opCode != OpCode.EXTCODECOPY // EXTCODECOPY needs the memory expansion cost
&& opCode != OpCode.INVALID // INVALID consumes all gas left
&& opCode != OpCode.MLOAD // MLOAD needs the memory expansion cost
&& opCode != OpCode.MSTORE // MSTORE needs the memory expansion cost
&& opCode != OpCode.MSTORE8 // MSTORE8 needs the memory expansion cost
&& opCode != OpCode.RETURN // RETURN needs the memory expansion cost
&& opCode != OpCode.RETURNDATACOPY // RETURNDATACOPY needs the memory expansion cost
&& opCode != OpCode.REVERT // REVERT needs the memory expansion cost
&& opCode != OpCode.SHA3 // SHA3 needs the memory expansion cost
&& opCode != OpCode.STOP // STOP does not consume gas
&& !opCodeData.isCall() // CALL family is managed separately
&& !opCodeData.isCreate() // CREATE needs the memory expansion cost
&& !opCodeData.isLog() // LOG needs the memory expansion cost
) {
arguments.add(Arguments.of(opCode, opCodeStaticCost, nPushes, -1));
arguments.add(Arguments.of(opCode, opCodeStaticCost, nPushes, 0));
arguments.add(Arguments.of(opCode, opCodeStaticCost, nPushes, 1));
}
}
return arguments.stream();
}

/*
@Test
void outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostSingleTest() {
OpCode opCode = OpCode.SSTORE;
int opCodeStaticCost = 0;
int nPushes = 2;
int cornerCase = 0;
outOfGasExceptionWithEmptyAccountsAndNoMemoryExpansionCostBody(
opCode, opCodeStaticCost, nPushes, cornerCase);
}
*/

@ParameterizedTest
@MethodSource("outOfGasExceptionCallSource")
void outOfGasExceptionCallTest(
int value, boolean targetAddressExists, boolean isWarm, int cornerCase) {
BytecodeCompiler program = BytecodeCompiler.newProgram();

if (targetAddressExists && isWarm) {
// Note: this is a possible way to warm the address
program.push("ca11ee").op(OpCode.BALANCE);
}

program
.push(0) // return at capacity
.push(0) // return at offset
.push(0) // call data size
.push(0) // call data offset
.push(value) // value
.push("ca11ee") // address
.push(1000) // gas
.op(OpCode.CALL);

BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile());

long gasCost =
GAS_CONST_G_TRANSACTION
+ // base gas cost
(isWarm ? GAS_CONST_G_VERY_LOW + GAS_CONST_G_COLD_ACCOUNT_ACCESS : 0) // PUSH + BALANCE
+ 7 * GAS_CONST_G_VERY_LOW // 7 PUSH
+ callGasCost(value != 0, targetAddressExists, isWarm); // CALL

if (targetAddressExists) {
final ToyAccount calleeAccount =
ToyAccount.builder()
.balance(Wei.fromEth(1))
.nonce(10)
.address(Address.fromHexString("ca11ee"))
.build();
bytecodeRunner.run(gasCost + cornerCase, List.of(calleeAccount));
} else {
bytecodeRunner.run(gasCost + cornerCase);
}

if (cornerCase == -1) {
assertEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
} else {
assertNotEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
}
}

static Stream<Arguments> outOfGasExceptionCallSource() {
List<Arguments> arguments = new ArrayList<>();
for (int value : new int[] {0, 1}) {
for (int cornerCase : new int[] {-1, 0, 1}) {
arguments.add(Arguments.of(value, true, true, cornerCase));
arguments.add(Arguments.of(value, true, false, cornerCase));
arguments.add(Arguments.of(value, false, false, cornerCase));
}
}
return arguments.stream();
}

private long callGasCost(boolean transfersValue, boolean targetAddressExists, boolean isWarm) {
Preconditions.checkArgument(
!(isWarm && !targetAddressExists), "isWarm implies targetAddressExists");
return (transfersValue ? GAS_CONST_G_CALL_VALUE : 0)
+ (targetAddressExists ? 0 : (transfersValue ? GAS_CONST_G_NEW_ACCOUNT : 0))
+ (isWarm ? GAS_CONST_G_WARM_ACCESS : GAS_CONST_G_COLD_ACCOUNT_ACCESS);
}

/**
* We provide a non-zero value in storage so to disambiguate between writing the value in storage
* to the stack and writing 0 to the stack.
*/
@ParameterizedTest
@ValueSource(ints = {-1, 0, 1})
void outOfGasExceptionSStore(int cornerCase) {
BytecodeCompiler program = BytecodeCompiler.newProgram();

program
.push(2) // value
.push(1) // key
.op(OpCode.SSTORE);

program
.push(1)
. // key
op(OpCode.SLOAD);

BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile());

long gasCost =
(long) GAS_CONST_G_TRANSACTION
+ (long) 2 * GAS_CONST_G_VERY_LOW // 2 PUSH
+ GAS_CONST_G_SSET
// SSTORE cost since current_value == original_value
// and original_value == 0 (20000)
+ GAS_CONST_G_COLD_SLOAD // SSTORE cost since slot is cold (2100)
+ (long) GAS_CONST_G_VERY_LOW // PUSH
+ GAS_CONST_G_WARM_ACCESS; // SLOAD (100)

bytecodeRunner.run(gasCost + cornerCase);

if (cornerCase == -1) {
assertEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
} else {
assertNotEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ public void run(List<ToyAccount> additionalAccounts) {
this.run(Wei.fromEth(1), (long) GlobalConstants.LINEA_BLOCK_GAS_LIMIT, additionalAccounts);
}

// Ad-hoc gasLimit and accounts
public void run(Long gasLimit, List<ToyAccount> additionalAccounts) {
this.run(Wei.fromEth(1), gasLimit, additionalAccounts);
}

// Ad-hoc senderBalance, gasLimit and accounts
public void run(Wei senderBalance, Long gasLimit, List<ToyAccount> additionalAccounts) {
checkArgument(byteCode != null, "byteCode cannot be empty");
Expand Down
Loading