diff --git a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java index 22de7df1ba..5b9e8d1410 100644 --- a/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java +++ b/arithmetization/src/main/java/net/consensys/linea/zktracer/module/hub/transients/OperationAncillaries.java @@ -19,6 +19,7 @@ import static net.consensys.linea.zktracer.module.UtilCalculator.allButOneSixtyFourth; import static net.consensys.linea.zktracer.types.AddressUtils.isPrecompile; +import com.google.common.base.Preconditions; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.consensys.linea.zktracer.module.constants.GlobalConstants; @@ -164,14 +165,36 @@ public static Bytes initCode(final MessageFrame frame) { public static MemorySpan returnDataRequestedSegment(final MessageFrame frame) { switch (OpCode.of(frame.getCurrentOperation().getOpcode())) { case CALL, CALLCODE -> { - long offset = Words.clampedToLong(frame.getStackItem(5)); - long length = Words.clampedToLong(frame.getStackItem(6)); - return MemorySpan.fromStartLength(offset, length); + long callDataOffset = Words.clampedToLong(frame.getStackItem(3)); + long callDataSize = Words.clampedToLong(frame.getStackItem(4)); + long returnAtOffset = Words.clampedToLong(frame.getStackItem(5)); + long returnAtCapacity = Words.clampedToLong(frame.getStackItem(6)); + + Preconditions.checkArgument(!(callDataOffset >= Math.pow(2, 32) && callDataSize == 0)); + Preconditions.checkArgument(!(returnAtOffset >= Math.pow(2, 32) && returnAtCapacity == 0)); + + System.out.println("callDataOffset: " + callDataOffset); + System.out.println("callDataSize: " + callDataSize); + System.out.println("returnAtOffset: " + returnAtOffset); + System.out.println("returnAtCapacity: " + returnAtCapacity); + + return MemorySpan.fromStartLength(returnAtOffset, returnAtCapacity); } case DELEGATECALL, STATICCALL -> { - long offset = Words.clampedToLong(frame.getStackItem(4)); - long length = Words.clampedToLong(frame.getStackItem(5)); - return MemorySpan.fromStartLength(offset, length); + long callDataOffset = Words.clampedToLong(frame.getStackItem(2)); + long callDataSize = Words.clampedToLong(frame.getStackItem(3)); + long returnAtOffset = Words.clampedToLong(frame.getStackItem(4)); + long returnAtCapacity = Words.clampedToLong(frame.getStackItem(5)); + + System.out.println("callDataOffset: " + callDataOffset); + System.out.println("callDataSize: " + callDataSize); + System.out.println("returnAtOffset: " + returnAtOffset); + System.out.println("returnDataSize: " + returnAtCapacity); + + Preconditions.checkArgument(!(callDataOffset >= Math.pow(2, 32) && callDataSize == 0)); + Preconditions.checkArgument(!(returnAtOffset >= Math.pow(2, 32) && returnAtCapacity == 0)); + + return MemorySpan.fromStartLength(returnAtOffset, returnAtCapacity); } default -> throw new IllegalArgumentException( "returnDataRequestedSegment called outside of a *CALL"); @@ -221,7 +244,7 @@ public static MemorySpan outputDataSpan(final MessageFrame frame) { // We cannot use this method for that purpose. case CALL, CALLCODE, DELEGATECALL, STATICCALL -> { Address target = Words.toAddress(frame.getStackItem(1)); - if (isPrecompile(target)) { + if (!isPrecompile(target)) { return MemorySpan.fromStartLength(0, 0); } checkArgument(isPrecompile(target)); // useless (?) sanity check diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/module/hub/ZeroSizeCallDataOrReturnDataTest.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/module/hub/ZeroSizeCallDataOrReturnDataTest.java new file mode 100644 index 0000000000..1651939120 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/module/hub/ZeroSizeCallDataOrReturnDataTest.java @@ -0,0 +1,108 @@ +/* + * Copyright ConsenSys Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package net.consensys.linea.zktracer.module.hub; + +import java.util.List; + +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 org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.junit.jupiter.api.Test; + +public class ZeroSizeCallDataOrReturnDataTest { + + @Test + void zeroSizeHugeReturnAtOffsetTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push("ff".repeat(32)) // return at offset + .push(0) // call data size + .push(0) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.op(OpCode.CALLDATASIZE); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + // TODO: this test is supposed to fail as the ones below, but it does not. Understand why + } + + @Test + void zeroSizeHugeCallDataOffsetTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push(0) // return at offset + .push(0) // call data size + .push("ff".repeat(32)) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.op(OpCode.CALLDATASIZE); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + } + + @Test + void zeroSizeHugeReturnDataOffsetTest() { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + program + .push(0) // return at capacity + .push(0) // return at offset + .push(0) // call data size + .push(0) // call data offset + .push("ca11ee") // address + .push(1000) // gas + .op(OpCode.STATICCALL); + + BytecodeCompiler calleeProgram = BytecodeCompiler.newProgram(); + calleeProgram.push(0).push("ff".repeat(32)).op(OpCode.RETURN); + + final ToyAccount calleeAccount = + ToyAccount.builder() + .balance(Wei.fromEth(1)) + .nonce(10) + .address(Address.fromHexString("ca11ee")) + .code(calleeProgram.compile()) + .build(); + + BytecodeRunner.of(program.compile()).run(Wei.fromEth(1), 30000L, List.of(calleeAccount)); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/module/mxp/MxpTest.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/module/mxp/MxpTest.java index ba70e798f0..f08cb20808 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/module/mxp/MxpTest.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/module/mxp/MxpTest.java @@ -40,15 +40,19 @@ import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; // https://github.com/Consensys/linea-besu-plugin/issues/197 @Execution(ExecutionMode.SAME_THREAD) public class MxpTest { - private static final Random RAND = new Random(123456789123456L); + private Random RAND; public static final EWord TWO_POW_128 = EWord.of(EWord.ONE.shiftLeft(128)); public static final EWord TWO_POW_32 = EWord.of(EWord.ONE.shiftLeft(32)); @@ -74,6 +78,13 @@ public class MxpTest { final OpCode[] opCodesHalting = new OpCode[] {OpCode.RETURN, OpCode.REVERT}; + @BeforeEach + void init() { + // The random generator is re-initialized before each test so to do not make them + // order-dependent + RAND = new Random(123456789123456L); + } + @Test void testMxpMinimalNonEmptyReturn() { BytecodeRunner.of(Bytes.fromHexString("6101006000f3")).run(); @@ -181,18 +192,42 @@ void testMxpxOrRoob() { @Test void testMxpRandomAdvanced() { - // Testing a random program that contains creates with meaning random arguments - Bytes INIT = getRandomINITForCreate(); // This is the value given as an argument to CREATE + // Testing a random program that contains creates with meaningful random arguments + Bytes INIT = getRandomINITForCreate(256); // This is the value given as an argument to CREATE BytecodeCompiler program = BytecodeCompiler.newProgram(); - int instructionCount = 256; - for (int i = 0; i < instructionCount; i++) { - boolean isHalting = i == instructionCount - 1; + int INSTRUCTION_COUNT = 256; + for (int i = 0; i < INSTRUCTION_COUNT; i++) { + boolean isHalting = i == INSTRUCTION_COUNT - 1; triggerNonTrivialOrNoop(program, isHalting, INIT); } BytecodeRunner.of(program.compile()).run(); } + @ParameterizedTest + @MethodSource("testMxpRandomAdvancedSource") + void testMxpRandomAdvancedWithFixedSeed( + long SEED, int INSTRUCTION_COUNT_INIT, int INSTRUCTION_COUNT) { + // Overwrite the class-level random generator with a new one with a fixed seed + RAND = new Random(SEED); + // Testing a random program that contains creates with meaningful random arguments + Bytes INIT = + getRandomINITForCreate( + INSTRUCTION_COUNT_INIT); // This is the value given as an argument to CREATE + + BytecodeCompiler program = BytecodeCompiler.newProgram(); + for (int i = 0; i < INSTRUCTION_COUNT; i++) { + boolean isHalting = i == INSTRUCTION_COUNT - 1; + triggerNonTrivialOrNoop(program, isHalting, INIT); + } + BytecodeRunner.of(program.compile()).run(); + } + + private static Stream testMxpRandomAdvancedSource() { + // Failing test cases + return Stream.of(Arguments.of(166L, 3, 2), Arguments.of(44L, 3, 3), Arguments.of(244L, 5, 5)); + } + @Test void testCall() { /* NOTE: The contracts in this test are compiled by using @@ -302,8 +337,7 @@ void testCall() { } // Support methods - private Bytes getRandomINITForCreate() { - final int INSTRUCTION_COUNT_INIT = 256; + private Bytes getRandomINITForCreate(final int INSTRUCTION_COUNT_INIT) { BytecodeCompiler INIT = BytecodeCompiler.newProgram(); for (int i = 0; i < INSTRUCTION_COUNT_INIT; i++) { boolean isHalting = i == INSTRUCTION_COUNT_INIT - 1; @@ -385,6 +419,8 @@ private void triggerNonTrivialOrNoop(BytecodeCompiler program, boolean isHalting appendOpCodeCall(List.of(value, offset1), opCode, program); break; case LOG0, SHA3, RETURN, REVERT: // RETURN and REVERT are selected only when isHalting is true + System.out.println( + opCode + " size: " + size1.toBigInteger() + " offset: " + offset1.toBigInteger()); appendOpCodeCall(List.of(size1, offset1), opCode, program); if (opCode == OpCode.SHA3) { program.op(OpCode.POP); @@ -548,9 +584,7 @@ private void triggerNonTrivialButMxpxOrRoob( } private boolean isRoob(MxpType randomMxpType, EWord size1, EWord offset1) { - final boolean condition4And5 = offset1.compareTo(TWO_POW_128) >= 0 && !size1.isZero(); - return switch (randomMxpType) { case TYPE_2, TYPE_3 -> offset1.compareTo(TWO_POW_128) >= 0; case TYPE_4 -> size1.compareTo(TWO_POW_128) >= 0 || condition4And5;