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 4 commits into
base: arith-dev
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ 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(
"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 @@ -26,28 +26,33 @@

import net.consensys.linea.testing.BytecodeCompiler;
import net.consensys.linea.testing.BytecodeRunner;
import net.consensys.linea.testing.ToyAccount;
import net.consensys.linea.zktracer.module.constants.GlobalConstants;
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.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class OutOfGasExceptionTest {

// TODO: add tests when address is warm. Use constants such as G_WARM_ACCESS etc
// TODO: add tests when address is warm for every opcode
@ParameterizedTest
@MethodSource("outOfGasExceptionSource")
void outOfGasExceptionColdTest(
OpCode opCode, int staticCost, int nPushes, boolean triggersOutOfGasExceptions) {
OpCode opCode, int opCodeStaticCost, int nPushes, short corneCase) {
BytecodeCompiler program = BytecodeCompiler.newProgram();
for (int i = 0; i < nPushes; i++) {
program.push(0);
}
program.op(opCode);
BytecodeRunner bytecodeRunner = BytecodeRunner.of(program.compile());
bytecodeRunner.run(
(long) 21000 + nPushes * 3L + staticCost - (triggersOutOfGasExceptions ? 1 : 0));
if (triggersOutOfGasExceptions) {
// TODO: check if this approach is general enough, maybe use a similar approach to the test
// below
bytecodeRunner.run((long) 21000 + nPushes * 3L + opCodeStaticCost + corneCase);
if (corneCase == -1) {
assertEquals(
OUT_OF_GAS_EXCEPTION,
bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException());
Expand All @@ -62,18 +67,108 @@ static Stream<Arguments> outOfGasExceptionSource() {
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
int opCodeStaticCost = opCodeData.stackSettings().staticGas().cost();
int nPushes = 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
if (opCodeStaticCost > 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));
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();
}

@ParameterizedTest
@MethodSource("outOfGasExceptionCallSource")
void outOfGasExceptionCallTest(
int value, boolean targetAddressExists, boolean isWarm, short 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 gasLimit =
21000L
+ // base gas cost
(isWarm ? 3L + 2600L : 0) // BALANCE + PUSH
+ 7 * 3L // 7 PUSH
+ callGasCost(value, targetAddressExists, isWarm); // CALL

if (targetAddressExists) {
final ToyAccount calleeAccount =
ToyAccount.builder()
.balance(Wei.fromEth(1))
.nonce(10)
.address(Address.fromHexString("ca11ee"))
.build();
bytecodeRunner.run(gasLimit + cornerCase, List.of(calleeAccount));
} else {
bytecodeRunner.run(gasLimit + 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 (short cornerCase : new short[] {-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(int value, boolean targetAddressExists, boolean isWarm) {
// TODO: check if this method is correct, general enough and can be simplified
if (value == 0) {
if (isWarm) {
return GlobalConstants.GAS_CONST_G_WARM_ACCESS;
} else {
return GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS;
}
} else {
if (isWarm) {
return GlobalConstants.GAS_CONST_G_WARM_ACCESS + GlobalConstants.GAS_CONST_G_CALL_VALUE;
} else {
if (targetAddressExists) {
return GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS
+ GlobalConstants.GAS_CONST_G_CALL_VALUE;
} else {
return GlobalConstants.GAS_CONST_G_NEW_ACCOUNT
+ GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS
+ GlobalConstants.GAS_CONST_G_CALL_VALUE;
}
}
}
}
}
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