From a85fc3be612a66a564075983a100bba118deda01 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Tue, 18 Feb 2025 15:44:19 +0100 Subject: [PATCH 01/16] added docs --- .../sixtyThreeSixtyFourthsTests.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java new file mode 100644 index 0000000000..e9a1a03281 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java @@ -0,0 +1,39 @@ +package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; + +import org.junit.jupiter.api.Test; + +/* + * Copyright Consensys Software 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 + */ +public class sixtyThreeSixtyFourthsTests { + + @Test + void sixtyThreeSixtyFourthsTest() { + /* + A transaction needs to target an SMC that will: + - expand memory by executing an MLOAD at offset 4096 - 32 = 4064 + - do a CALL type instruction to a precompile contract + - the gas we provide to the precompile contract should be: + providedGas = 63/64 * (remainingGas - upfrontGasCost) + callStipend * (valueIsNonZero ? 1 : 0). + this gas should be insufficient to pay for the execution of the precompile contract (cornerCase = -1, 0, 1). + This means that I need to find the GAS_LIMIT for the transaction such that providedGas = precompileGasCost + cornerCase. + Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the MLOAD, PUSHEes etc and the 21000, + GAS_CONST_G_TRANSACTION). + NOTE: in case the precompile contract does not exist in the world state, we may pay an additional 25000 gas cost when + transferring value in the call from SMC to PRC. We need to check if they exist in the world state. + An option may be sending some value to the contract first to do not pay this 25000 during the test. + */ + + } +} From 5269c8082d2e297b065b18797c6a2033151997c0 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Thu, 20 Feb 2025 00:09:57 +0100 Subject: [PATCH 02/16] added test for ECADD case --- .../sixtyThreeSixtyFourthsTests.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java index e9a1a03281..e85aa34633 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java @@ -1,5 +1,18 @@ package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; +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_NEW_ACCOUNT; +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 org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.consensys.linea.testing.BytecodeCompiler; +import net.consensys.linea.testing.BytecodeRunner; +import net.consensys.linea.zktracer.opcode.OpCode; import org.junit.jupiter.api.Test; /* @@ -35,5 +48,41 @@ Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the ML An option may be sending some value to the contract first to do not pay this 25000 during the test. */ + final BytecodeCompiler program = BytecodeCompiler.newProgram(); + + program.push(4096 - 32).op(OpCode.MLOAD); + + long cost = + GAS_CONST_G_TRANSACTION + + GAS_CONST_G_VERY_LOW // PUSH + + GAS_CONST_G_VERY_LOW + + 416 // MLOAD + + GAS_CONST_G_VERY_LOW * 7 // 7 PUSHes + ; // + callGasCostExcludingMemoryExpansion(false, true, false); ? + + program + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(ALTBN128_ADD) // address + .push(150) // gas + .op(OpCode.CALL); + + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + + bytecodeRunner.run(cost - 1); + + assertEquals( + OUT_OF_GAS_EXCEPTION, + bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + } + + private long callGasCostExcludingMemoryExpansion( + boolean transfersValue, boolean targetAddressExists, boolean isWarm) { + 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); } } From f862f0d5aadc40a5a5f819cfdd4e2c4dc6acaa07 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Thu, 20 Feb 2025 18:08:42 +0100 Subject: [PATCH 03/16] fixed ecadd test and added ecmul case --- .../SixtyThreeSixtyFourthsTests.java | 101 ++++++++++++++++++ .../sixtyThreeSixtyFourthsTests.java | 88 --------------- .../zktracer/precompiles/PrecompileUtils.java | 18 ++-- 3 files changed, 110 insertions(+), 97 deletions(-) create mode 100644 arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java delete mode 100644 arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java new file mode 100644 index 0000000000..fada05b381 --- /dev/null +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -0,0 +1,101 @@ +package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; + +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getECADDCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getECMULCost; +import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; +import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.consensys.linea.testing.BytecodeCompiler; +import net.consensys.linea.testing.BytecodeRunner; +import net.consensys.linea.zktracer.module.oob.OobOperation; +import net.consensys.linea.zktracer.opcode.OpCode; +import org.junit.jupiter.api.Test; + +/* + * Copyright Consensys Software 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 + */ +public class SixtyThreeSixtyFourthsTests { + + /* + A transaction needs to target an SMC that will: + - expand memory by executing an MLOAD at offset 4096 - 32 = 4064 + - do a CALL type instruction to a precompile contract + - the gas we provide to the precompile contract should be: + providedGas = 63/64 * (remainingGasBeforeCall - callOpcodeCost) + callStipend * (valueIsNonZero ? 1 : 0). + this gas should be insufficient to pay for the execution of the precompile contract (cornerCase = -1, 0, 1). + This means that I need to find the GAS_LIMIT for the transaction such that providedGas = precompileGasCost + cornerCase. + Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the MLOAD, PUSHEes etc and the 21000, + GAS_CONST_G_TRANSACTION). + NOTE: in case the precompile contract does not exist in the world state, we may pay an additional 25000 gas cost when + transferring value in the call from SMC to PRC. We need to check if they exist in the world state. + An option may be sending some value to the contract first to do not pay this 25000 during the test. + */ + + @Test + void sixtyThreeSixtyFourthsEcAddTest() { + final BytecodeCompiler program = BytecodeCompiler.newProgram(); + + program.push(4096 - 32).op(OpCode.MLOAD); + program + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(ALTBN128_ADD) // address + .push(getECADDCost() * 10) // gas + .op(OpCode.CALL); + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + + final long gasCost = bytecodeRunner.runOnlyForGasCost(); + bytecodeRunner.run(gasCost); + + // providedGas = 63/64 * (250 - 100) + 2300 * 0 = 148 > 150 + // Indeed, without the 63/64 factor, the providedGas would be enough + + final boolean insufficientGasForPrecompile = + bytecodeRunner.getHub().oob().operations().stream() + .anyMatch(OobOperation::isInsufficientGasForPrecompile); + assertTrue(insufficientGasForPrecompile); + } + + @Test + void sixtyThreeSixtyFourthsEcMulTest() { + final BytecodeCompiler program = BytecodeCompiler.newProgram(); + + program.push(4096 - 32).op(OpCode.MLOAD); + program + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(ALTBN128_MUL) // address + .push(getECMULCost()*10) // gas + .op(OpCode.CALL); + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + + final long gasCost = bytecodeRunner.runOnlyForGasCost(); + bytecodeRunner.run(gasCost); + + // providedGas = 63/64 * (6100 - 100) + 2300 * 0 = 5907 > 6000 + // Indeed, without the 63/64 factor, the providedGas would be enough + + final boolean insufficientGasForPrecompile = + bytecodeRunner.getHub().oob().operations().stream() + .anyMatch(OobOperation::isInsufficientGasForPrecompile); + assertTrue(insufficientGasForPrecompile); + } +} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java deleted file mode 100644 index e85aa34633..0000000000 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/sixtyThreeSixtyFourthsTests.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; - -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_NEW_ACCOUNT; -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 org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import net.consensys.linea.testing.BytecodeCompiler; -import net.consensys.linea.testing.BytecodeRunner; -import net.consensys.linea.zktracer.opcode.OpCode; -import org.junit.jupiter.api.Test; - -/* - * Copyright Consensys Software 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 - */ -public class sixtyThreeSixtyFourthsTests { - - @Test - void sixtyThreeSixtyFourthsTest() { - /* - A transaction needs to target an SMC that will: - - expand memory by executing an MLOAD at offset 4096 - 32 = 4064 - - do a CALL type instruction to a precompile contract - - the gas we provide to the precompile contract should be: - providedGas = 63/64 * (remainingGas - upfrontGasCost) + callStipend * (valueIsNonZero ? 1 : 0). - this gas should be insufficient to pay for the execution of the precompile contract (cornerCase = -1, 0, 1). - This means that I need to find the GAS_LIMIT for the transaction such that providedGas = precompileGasCost + cornerCase. - Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the MLOAD, PUSHEes etc and the 21000, - GAS_CONST_G_TRANSACTION). - NOTE: in case the precompile contract does not exist in the world state, we may pay an additional 25000 gas cost when - transferring value in the call from SMC to PRC. We need to check if they exist in the world state. - An option may be sending some value to the contract first to do not pay this 25000 during the test. - */ - - final BytecodeCompiler program = BytecodeCompiler.newProgram(); - - program.push(4096 - 32).op(OpCode.MLOAD); - - long cost = - GAS_CONST_G_TRANSACTION - + GAS_CONST_G_VERY_LOW // PUSH - + GAS_CONST_G_VERY_LOW - + 416 // MLOAD - + GAS_CONST_G_VERY_LOW * 7 // 7 PUSHes - ; // + callGasCostExcludingMemoryExpansion(false, true, false); ? - - program - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(ALTBN128_ADD) // address - .push(150) // gas - .op(OpCode.CALL); - - final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - - bytecodeRunner.run(cost - 1); - - assertEquals( - OUT_OF_GAS_EXCEPTION, - bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); - } - - private long callGasCostExcludingMemoryExpansion( - boolean transfersValue, boolean targetAddressExists, boolean isWarm) { - 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); - } -} diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java index f72953168d..90f2ce9f18 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java @@ -80,45 +80,45 @@ public static int getPrecompileCost( } } - private static int getECRECCost() { + public static int getECRECCost() { return GAS_CONST_ECRECOVER; } - private static int getSHA256Cost(int cds) { + public static int getSHA256Cost(int cds) { final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; return GAS_CONST_SHA2 + words * GAS_CONST_SHA2_WORD; } - private static int getRIPEMD160Cost(int cds) { + public static int getRIPEMD160Cost(int cds) { final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; return GAS_CONST_RIPEMD + words * GAS_CONST_RIPEMD_WORD; } - private static int getIDCost(int cds) { + public static int getIDCost(int cds) { final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; return GAS_CONST_IDENTITY + words * GAS_CONST_IDENTITY_WORD; } - static int getMODEXPCost(int bbs, int mbs, int exponentLog) { + public static int getMODEXPCost(int bbs, int mbs, int exponentLog) { final int fOfMax = ((Math.max(bbs, mbs) + 7) / 8) * ((Math.max(bbs, mbs) + 7) / 8); final int bigNumerator = fOfMax * Math.max(exponentLog, 1); final int bigQuotient = bigNumerator / G_QUADDIVISOR; return Math.max(GAS_CONST_MODEXP, bigQuotient); } - private static int getECADDCost() { + public static int getECADDCost() { return GAS_CONST_ECADD; } - private static int getECMULCost() { + public static int getECMULCost() { return GAS_CONST_ECMUL; } - private static int getECPAIRINGCost(int cds) { + public static int getECPAIRINGCost(int cds) { return GAS_CONST_ECPAIRING + GAS_CONST_ECPAIRING_PAIR * (cds / PRC_ECPAIRING_SIZE); } - private static int getBLAKE2FCost(int r) { + public static int getBLAKE2FCost(int r) { return GAS_CONST_BLAKE2_PER_ROUND * r; } } From e2ab2f08a65503c7443e753a9fb3c181f05f7a46 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Thu, 20 Feb 2025 18:11:16 +0100 Subject: [PATCH 04/16] set gas parameter to max possible value --- .../SixtyThreeSixtyFourthsTests.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index fada05b381..3aee9164df 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -10,6 +10,7 @@ import net.consensys.linea.testing.BytecodeRunner; import net.consensys.linea.zktracer.module.oob.OobOperation; import net.consensys.linea.zktracer.opcode.OpCode; +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; /* @@ -43,6 +44,8 @@ Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the ML An option may be sending some value to the contract first to do not pay this 25000 during the test. */ + final Bytes gas = Bytes.fromHexString("ff".repeat(32)); + @Test void sixtyThreeSixtyFourthsEcAddTest() { final BytecodeCompiler program = BytecodeCompiler.newProgram(); @@ -55,7 +58,7 @@ void sixtyThreeSixtyFourthsEcAddTest() { .push(0) // callDataOffset .push(0) // value .push(ALTBN128_ADD) // address - .push(getECADDCost() * 10) // gas + .push(gas) // gas .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); @@ -83,7 +86,7 @@ void sixtyThreeSixtyFourthsEcMulTest() { .push(0) // callDataOffset .push(0) // value .push(ALTBN128_MUL) // address - .push(getECMULCost()*10) // gas + .push(gas) // gas .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); From 824152be309aee86a228d949713ea0a0b833cf87 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Thu, 20 Feb 2025 18:28:47 +0100 Subject: [PATCH 05/16] added ecadd test with value case --- .../SixtyThreeSixtyFourthsTests.java | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 3aee9164df..5eccb4b262 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -1,13 +1,12 @@ package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; -import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getECADDCost; -import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getECMULCost; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.linea.testing.BytecodeCompiler; import net.consensys.linea.testing.BytecodeRunner; +import net.consensys.linea.zktracer.module.constants.GlobalConstants; import net.consensys.linea.zktracer.module.oob.OobOperation; import net.consensys.linea.zktracer.opcode.OpCode; import org.apache.tuweni.bytes.Bytes; @@ -62,7 +61,7 @@ void sixtyThreeSixtyFourthsEcAddTest() { .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - final long gasCost = bytecodeRunner.runOnlyForGasCost(); + final long gasCost = bytecodeRunner.runOnlyForGasCost(); // 21693 bytecodeRunner.run(gasCost); // providedGas = 63/64 * (250 - 100) + 2300 * 0 = 148 > 150 @@ -75,30 +74,68 @@ void sixtyThreeSixtyFourthsEcAddTest() { } @Test - void sixtyThreeSixtyFourthsEcMulTest() { + void sixtyThreeSixtyFourthsEcAddTestWithValue() { final BytecodeCompiler program = BytecodeCompiler.newProgram(); program.push(4096 - 32).op(OpCode.MLOAD); program - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(ALTBN128_MUL) // address - .push(gas) // gas - .op(OpCode.CALL); + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(1) // value + .push(ALTBN128_ADD) // address + .push(gas) // gas + .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); final long gasCost = bytecodeRunner.runOnlyForGasCost(); + // 53393 = 21693 + GlobalConstants.GAS_CONST_G_CALL_VALUE + GlobalConstants.GAS_CONST_G_NEW_ACCOUNT + bytecodeRunner.run(gasCost); + + // providedGas = ... + // Indeed, without the 63/64 factor, the providedGas would be enough + + final boolean insufficientGasForPrecompile = + bytecodeRunner.getHub().oob().operations().stream() + .anyMatch(OobOperation::isInsufficientGasForPrecompile); + assertTrue(insufficientGasForPrecompile); + } + + @Test + void sixtyThreeSixtyFourthsEcMulTest() { + final BytecodeCompiler program = BytecodeCompiler.newProgram(); + + program.push(4096 - 32).op(OpCode.MLOAD); + program + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(ALTBN128_MUL) // address + .push(gas) // gas + .op(OpCode.CALL); + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + + final long gasCost = bytecodeRunner.runOnlyForGasCost(); // 27543 bytecodeRunner.run(gasCost); // providedGas = 63/64 * (6100 - 100) + 2300 * 0 = 5907 > 6000 // Indeed, without the 63/64 factor, the providedGas would be enough final boolean insufficientGasForPrecompile = - bytecodeRunner.getHub().oob().operations().stream() - .anyMatch(OobOperation::isInsufficientGasForPrecompile); + bytecodeRunner.getHub().oob().operations().stream() + .anyMatch(OobOperation::isInsufficientGasForPrecompile); assertTrue(insufficientGasForPrecompile); } + + private long callGasCostExcludingMemoryExpansion( + boolean transfersValue, boolean targetAddressExists, boolean isWarm) { + return (transfersValue ? GlobalConstants.GAS_CONST_G_CALL_VALUE : 0) + + (targetAddressExists ? 0 : (transfersValue ? GlobalConstants.GAS_CONST_G_NEW_ACCOUNT : 0)) + + (isWarm + ? GlobalConstants.GAS_CONST_G_WARM_ACCESS + : GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS); + } } From 6c27b195b6dc7114a27ce50a516a8685bebf39a8 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Fri, 21 Feb 2025 09:41:48 +0100 Subject: [PATCH 06/16] added docs --- .../SixtyThreeSixtyFourthsTests.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 5eccb4b262..87e208e678 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -41,6 +41,14 @@ Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the ML NOTE: in case the precompile contract does not exist in the world state, we may pay an additional 25000 gas cost when transferring value in the call from SMC to PRC. We need to check if they exist in the world state. An option may be sending some value to the contract first to do not pay this 25000 during the test. + + Cases to cover: + - value = 0 + - value = 1, targetAddressExists = false, true + + Optionally: + - memoryExpansionBeforeCallToPrc = false, true, value = 0 + - memoryExpansionBeforeCallToPrc = false, true, value = 1, targetAddressExists = false, true */ final Bytes gas = Bytes.fromHexString("ff".repeat(32)); From c21c066c97bf9be159453c477c92b62a0a7668f9 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Fri, 21 Feb 2025 14:04:27 +0100 Subject: [PATCH 07/16] added documentation and highlighted issue with the current tests --- .../SixtyThreeSixtyFourthsTests.java | 87 +++++++++++++++---- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 87e208e678..590f62a6c7 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -1,7 +1,9 @@ package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; +import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import net.consensys.linea.testing.BytecodeCompiler; @@ -32,27 +34,54 @@ public class SixtyThreeSixtyFourthsTests { A transaction needs to target an SMC that will: - expand memory by executing an MLOAD at offset 4096 - 32 = 4064 - do a CALL type instruction to a precompile contract - - the gas we provide to the precompile contract should be: - providedGas = 63/64 * (remainingGasBeforeCall - callOpcodeCost) + callStipend * (valueIsNonZero ? 1 : 0). - this gas should be insufficient to pay for the execution of the precompile contract (cornerCase = -1, 0, 1). - This means that I need to find the GAS_LIMIT for the transaction such that providedGas = precompileGasCost + cornerCase. - Specifically, the GAS_LIMIT influences the remainingGas (note that we pay the MLOAD, PUSHEes etc and the 21000, - GAS_CONST_G_TRANSACTION). - NOTE: in case the precompile contract does not exist in the world state, we may pay an additional 25000 gas cost when - transferring value in the call from SMC to PRC. We need to check if they exist in the world state. - An option may be sending some value to the contract first to do not pay this 25000 during the test. + - the gas we provide to the precompile contract should cover the cases below. Cases to cover: - value = 0 - - value = 1, targetAddressExists = false, true + - value = 1 | targetAddressExists = false, true Optionally: - - memoryExpansionBeforeCallToPrc = false, true, value = 0 - - memoryExpansionBeforeCallToPrc = false, true, value = 1, targetAddressExists = false, true + - memoryExpansionBeforeCallToPrc = false, true | value = 0 + - memoryExpansionBeforeCallToPrc = false, true | value = 1 | targetAddressExists = false, true + + * Generic case: + executionCostOfProgramBeforeFinalCallToPRC = + value = 0 || !targetAddressExists : 21000 + MLOAD + PUSHEes + value = 1 && targetAddressExists : 21000 + MLOAD + PUSHEes + CALL (to send value to the precompile so as targetAddressExists = true) + remainingGasBeforeCall = gasLimit - executionCostOfProgramBeforeFinalCallToPRC + callOpcodeCost = 100 + (value > 0 && !targetAddressExists ? 25000 : 0) + (value > 0 ? 9000 : 0) + + For cornerCase = -1, 0 find gasLimit such that: + providedGas = 63/64 * (remainingGasBeforeCall - callOpcodeCost) + (value > 0 ? 2300 : 0) = precompileGasCost + cornerCase + + Note that the value > 0 case is meaningful only when + precompileGasCost + cornerCase >= 2300 as otherwise being able to pay for the call, + that is remainingGasBeforeCall - callOpcodeCost >= 0, implies we can pay for the precompile, too (and we are + interested in the case in which we can't pay for the precompile). + + Let x = remainingGasBeforeCall + Let y = gasLimit + + Find y = x - executionCostOfProgramBeforeFinalCallToPRC such that: + + * value = 0 + 63/64 * (x - 100) = precompileGasCost - 1, precompileGasCost + + * value = 1, targetAddressExists = false + 63/64 * (x - 100 - 25000 - 9000) + 2300 = precompileGasCost - 1, precompileGasCost + + * value = 1, targetAddressExists = true + 63/64 * (x - 100 - 9000) + 2300 = precompileGasCost - 1, precompileGasCost + + BLAKE2F is the only case that requires a input that is not 0 to have a cost greater than 2300. + Otherwise, call data size is the only aspect we care. */ final Bytes gas = Bytes.fromHexString("ff".repeat(32)); + // TODO: check the documentation here is consistent with the one in the issue and change the tests + // below accordingly + @Test void sixtyThreeSixtyFourthsEcAddTest() { final BytecodeCompiler program = BytecodeCompiler.newProgram(); @@ -69,12 +98,17 @@ void sixtyThreeSixtyFourthsEcAddTest() { .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - final long gasCost = bytecodeRunner.runOnlyForGasCost(); // 21693 + final long gasCost = bytecodeRunner.runOnlyForGasCost(); + // 21693 bytecodeRunner.run(gasCost); // providedGas = 63/64 * (250 - 100) + 2300 * 0 = 148 > 150 // Indeed, without the 63/64 factor, the providedGas would be enough + assertNotEquals( + OUT_OF_GAS_EXCEPTION, + bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + final boolean insufficientGasForPrecompile = bytecodeRunner.getHub().oob().operations().stream() .anyMatch(OobOperation::isInsufficientGasForPrecompile); @@ -98,16 +132,24 @@ void sixtyThreeSixtyFourthsEcAddTestWithValue() { final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); final long gasCost = bytecodeRunner.runOnlyForGasCost(); - // 53393 = 21693 + GlobalConstants.GAS_CONST_G_CALL_VALUE + GlobalConstants.GAS_CONST_G_NEW_ACCOUNT - bytecodeRunner.run(gasCost); + // 53393 = 21693 + 9000 + 25000 - 2300 + bytecodeRunner.run(gasCost + (2300 - 150)); - // providedGas = ... - // Indeed, without the 63/64 factor, the providedGas would be enough + // providedGas = + // 63/64 * (31950 + (2300 - 150) - 9000 - 25000 - 100) + 2300 * 1 = 2300 > 150 + // As long as we can pay for the call, we can pay for the precompile + + assertNotEquals( + OUT_OF_GAS_EXCEPTION, + bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + + /* final boolean insufficientGasForPrecompile = bytecodeRunner.getHub().oob().operations().stream() .anyMatch(OobOperation::isInsufficientGasForPrecompile); assertTrue(insufficientGasForPrecompile); + */ } @Test @@ -126,12 +168,17 @@ void sixtyThreeSixtyFourthsEcMulTest() { .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - final long gasCost = bytecodeRunner.runOnlyForGasCost(); // 27543 + final long gasCost = bytecodeRunner.runOnlyForGasCost(); + // 27543 bytecodeRunner.run(gasCost); // providedGas = 63/64 * (6100 - 100) + 2300 * 0 = 5907 > 6000 // Indeed, without the 63/64 factor, the providedGas would be enough + assertNotEquals( + OUT_OF_GAS_EXCEPTION, + bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + final boolean insufficientGasForPrecompile = bytecodeRunner.getHub().oob().operations().stream() .anyMatch(OobOperation::isInsufficientGasForPrecompile); @@ -140,6 +187,10 @@ void sixtyThreeSixtyFourthsEcMulTest() { private long callGasCostExcludingMemoryExpansion( boolean transfersValue, boolean targetAddressExists, boolean isWarm) { + // GAS_CONST_G_CALL_VALUE = 9000 + // GAS_CONST_G_NEW_ACCOUNT = 25000 + // GAS_CONST_G_WARM_ACCESS = 100 + // GAS_CONST_G_COLD_ACCOUNT_ACCESS = 2600 return (transfersValue ? GlobalConstants.GAS_CONST_G_CALL_VALUE : 0) + (targetAddressExists ? 0 : (transfersValue ? GlobalConstants.GAS_CONST_G_NEW_ACCOUNT : 0)) + (isWarm From 39b4caa7b7f9ba1788027030b1fbbbebbaac5b05 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Fri, 21 Feb 2025 14:48:44 +0100 Subject: [PATCH 08/16] added docs --- .../sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 590f62a6c7..e0209fb5f7 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -73,7 +73,8 @@ public class SixtyThreeSixtyFourthsTests { * value = 1, targetAddressExists = true 63/64 * (x - 100 - 9000) + 2300 = precompileGasCost - 1, precompileGasCost - BLAKE2F is the only case that requires a input that is not 0 to have a cost greater than 2300. + BLAKE2F requires an input that is not 0 to have a cost greater than 2300. + MODEXP also requires a special treatment to get a cost greater than 2300 (as it may be 200). Otherwise, call data size is the only aspect we care. */ From 7bdb0322e919949ff903b2e8a5e4bbc84467fc8a Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Fri, 21 Feb 2025 22:27:57 +0100 Subject: [PATCH 09/16] fixed approach to testing and added machinery --- .../SixtyThreeSixtyFourthsTests.java | 278 +++++++++--------- 1 file changed, 147 insertions(+), 131 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index e0209fb5f7..28a55a60cb 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -1,18 +1,42 @@ package net.consensys.linea.zktracer.instructionprocessing.callTests.sixtyThreeSixtyFourths; +import static com.google.common.base.Preconditions.checkArgument; +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_NEW_ACCOUNT; +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 org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; +import static org.hyperledger.besu.datatypes.Address.ALTBN128_PAIRING; +import static org.hyperledger.besu.datatypes.Address.BLAKE2B_F_COMPRESSION; +import static org.hyperledger.besu.datatypes.Address.ECREC; +import static org.hyperledger.besu.datatypes.Address.ID; +import static org.hyperledger.besu.datatypes.Address.MODEXP; +import static org.hyperledger.besu.datatypes.Address.RIPEMD160; +import static org.hyperledger.besu.datatypes.Address.SHA256; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.google.common.base.Function; import net.consensys.linea.testing.BytecodeCompiler; import net.consensys.linea.testing.BytecodeRunner; import net.consensys.linea.zktracer.module.constants.GlobalConstants; import net.consensys.linea.zktracer.module.oob.OobOperation; import net.consensys.linea.zktracer.opcode.OpCode; +import net.consensys.linea.zktracer.precompiles.PrecompileUtils; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; +import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /* * Copyright Consensys Software Inc. @@ -28,63 +52,94 @@ * * SPDX-License-Identifier: Apache-2.0 */ + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class SixtyThreeSixtyFourthsTests { /* - A transaction needs to target an SMC that will: - - expand memory by executing an MLOAD at offset 4096 - 32 = 4064 - - do a CALL type instruction to a precompile contract - - the gas we provide to the precompile contract should cover the cases below. - - Cases to cover: - - value = 0 - - value = 1 | targetAddressExists = false, true - - Optionally: - - memoryExpansionBeforeCallToPrc = false, true | value = 0 - - memoryExpansionBeforeCallToPrc = false, true | value = 1 | targetAddressExists = false, true - - * Generic case: - executionCostOfProgramBeforeFinalCallToPRC = - value = 0 || !targetAddressExists : 21000 + MLOAD + PUSHEes - value = 1 && targetAddressExists : 21000 + MLOAD + PUSHEes + CALL (to send value to the precompile so as targetAddressExists = true) - remainingGasBeforeCall = gasLimit - executionCostOfProgramBeforeFinalCallToPRC - callOpcodeCost = 100 + (value > 0 && !targetAddressExists ? 25000 : 0) + (value > 0 ? 9000 : 0) - - For cornerCase = -1, 0 find gasLimit such that: - providedGas = 63/64 * (remainingGasBeforeCall - callOpcodeCost) + (value > 0 ? 2300 : 0) = precompileGasCost + cornerCase - - Note that the value > 0 case is meaningful only when - precompileGasCost + cornerCase >= 2300 as otherwise being able to pay for the call, - that is remainingGasBeforeCall - callOpcodeCost >= 0, implies we can pay for the precompile, too (and we are - interested in the case in which we can't pay for the precompile). - - Let x = remainingGasBeforeCall - Let y = gasLimit - - Find y = x - executionCostOfProgramBeforeFinalCallToPRC such that: - - * value = 0 - 63/64 * (x - 100) = precompileGasCost - 1, precompileGasCost + Cases to cover: + * value = 0 + If precompileGasCost >= 2300 then we are interested in: * value = 1, targetAddressExists = false - 63/64 * (x - 100 - 25000 - 9000) + 2300 = precompileGasCost - 1, precompileGasCost - * value = 1, targetAddressExists = true - 63/64 * (x - 100 - 9000) + 2300 = precompileGasCost - 1, precompileGasCost - BLAKE2F requires an input that is not 0 to have a cost greater than 2300. - MODEXP also requires a special treatment to get a cost greater than 2300 (as it may be 200). - Otherwise, call data size is the only aspect we care. - */ + Note: BLAKE2F and MODEXP requires a non-zero non-trivial input to have a cost greater than 2300. + Other precompiles only require a proper call data size. + */ final Bytes gas = Bytes.fromHexString("ff".repeat(32)); - // TODO: check the documentation here is consistent with the one in the issue and change the tests - // below accordingly + // Generic program before the final call to the precompile when the target address does not exist + final BytecodeCompiler preCallTargetAddressDoesNotExistProgram = + BytecodeCompiler.newProgram() + .push(4096 - 32) + .op(OpCode.MLOAD) + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(0) // address (this is 0 as the cost of PUSH is always the same) + .push(gas); // gas + + // Cost of the generic program before the final call to the precompile when the target address + // does not exist + final long preCallTargetAddressDoesNotExistProgramGas = + BytecodeRunner.of(preCallTargetAddressDoesNotExistProgram).runOnlyForGasCost(); + + // Cost of the generic program before the final call to the precompile when the target address + // exists + Map preCallTargetAddressExistsProgramGasMap = new HashMap<>(); + + @BeforeAll + void computePreCallTargetAddressExistsProgramGasOnceForAll() { + // Generic program before the final call to the precompile when the target address exists + final Function preCallTargetAddressExistsProgram = + (address) -> + BytecodeCompiler.newProgram() + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(address) // address + .push(gas) // gas + .op(OpCode.CALL) + .push(4096 - 32) + .op(OpCode.MLOAD) + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(0) // callDataSize + .push(0) // callDataOffset + .push(0) // value + .push(0) // address (this is 0 as the cost of PUSH is always the same) + .push(gas); // gas + // Cost of the generic program before the final call to the precompile when the target address + // exists + final Function preCallTargetAddressExistsProgramGas = + (address) -> + BytecodeRunner.of(preCallTargetAddressExistsProgram.apply(address)).runOnlyForGasCost(); + for (Address address : + List.of( + ECREC, + SHA256, + RIPEMD160, + ID, + MODEXP, + ALTBN128_ADD, + ALTBN128_MUL, + ALTBN128_PAIRING, + BLAKE2B_F_COMPRESSION)) { + preCallTargetAddressExistsProgramGasMap.put( + address, preCallTargetAddressExistsProgramGas.apply(address)); + } + } - @Test - void sixtyThreeSixtyFourthsEcAddTest() { + @ParameterizedTest + @MethodSource("sixtyThreeSixtyFourthsEcAddTestSource") + void sixtyThreeSixtyFourthsEcAddTest( + long gasLimit, boolean insufficientGasForPrecompileExpected) { final BytecodeCompiler program = BytecodeCompiler.newProgram(); program.push(4096 - 32).op(OpCode.MLOAD); @@ -98,104 +153,65 @@ void sixtyThreeSixtyFourthsEcAddTest() { .push(gas) // gas .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + bytecodeRunner.run(gasLimit); - final long gasCost = bytecodeRunner.runOnlyForGasCost(); - // 21693 - bytecodeRunner.run(gasCost); - - // providedGas = 63/64 * (250 - 100) + 2300 * 0 = 148 > 150 - // Indeed, without the 63/64 factor, the providedGas would be enough + // insufficientGasForPrecompileExpected = true => targetCalleeGas = 63/64 * (252 - 100) = 150 + // insufficientGasForPrecompileExpected = false => targetCalleeGas = 63/64 * (251 - 100) = 149 assertNotEquals( OUT_OF_GAS_EXCEPTION, bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); - final boolean insufficientGasForPrecompile = + final boolean insufficientGasForPrecompileActual = bytecodeRunner.getHub().oob().operations().stream() .anyMatch(OobOperation::isInsufficientGasForPrecompile); - assertTrue(insufficientGasForPrecompile); - } - - @Test - void sixtyThreeSixtyFourthsEcAddTestWithValue() { - final BytecodeCompiler program = BytecodeCompiler.newProgram(); - program.push(4096 - 32).op(OpCode.MLOAD); - program - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(1) // value - .push(ALTBN128_ADD) // address - .push(gas) // gas - .op(OpCode.CALL); - final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - - final long gasCost = bytecodeRunner.runOnlyForGasCost(); - // 53393 = 21693 + 9000 + 25000 - 2300 - bytecodeRunner.run(gasCost + (2300 - 150)); - - // providedGas = - // 63/64 * (31950 + (2300 - 150) - 9000 - 25000 - 100) + 2300 * 1 = 2300 > 150 - - // As long as we can pay for the call, we can pay for the precompile - - assertNotEquals( - OUT_OF_GAS_EXCEPTION, - bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); - - /* - final boolean insufficientGasForPrecompile = - bytecodeRunner.getHub().oob().operations().stream() - .anyMatch(OobOperation::isInsufficientGasForPrecompile); - assertTrue(insufficientGasForPrecompile); - */ + assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual); } - @Test - void sixtyThreeSixtyFourthsEcMulTest() { - final BytecodeCompiler program = BytecodeCompiler.newProgram(); - - program.push(4096 - 32).op(OpCode.MLOAD); - program - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(ALTBN128_MUL) // address - .push(gas) // gas - .op(OpCode.CALL); - final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - - final long gasCost = bytecodeRunner.runOnlyForGasCost(); - // 27543 - bytecodeRunner.run(gasCost); - - // providedGas = 63/64 * (6100 - 100) + 2300 * 0 = 5907 > 6000 - // Indeed, without the 63/64 factor, the providedGas would be enough - - assertNotEquals( - OUT_OF_GAS_EXCEPTION, - bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + Stream sixtyThreeSixtyFourthsEcAddTestSource() { + List arguments = new ArrayList<>(); + final long targetCalleeGas = PrecompileUtils.getECADDCost(); + final long gasLimitEnough = + getGasLimit(targetCalleeGas, false, true, preCallTargetAddressDoesNotExistProgramGas); + final long gasLimitNotEnough = + getGasLimit(targetCalleeGas - 1, false, true, preCallTargetAddressDoesNotExistProgramGas); + arguments.add(Arguments.of(gasLimitEnough, false)); + arguments.add(Arguments.of(gasLimitNotEnough, true)); + return arguments.stream(); + } - final boolean insufficientGasForPrecompile = - bytecodeRunner.getHub().oob().operations().stream() - .anyMatch(OobOperation::isInsufficientGasForPrecompile); - assertTrue(insufficientGasForPrecompile); + // Support methods + long getGasLimit( + long targetCalleeGas, + boolean transfersValue, + boolean targetAddressExists, + long preCallProgramGas) { + /* gasLimit = preCallProgramGasCost + gasPreCall + / 63/64 * (gasPreCall - gasUpFront) + stipend = targetCalleeGas + / x = gasPreCall - gasUpFront + / k = x / 64 (integer division) + / l = x - 64 * k + / 63 * k + l + stipend = targetCalleeGas + / find gasLimit going backwards + */ + final long stipend = transfersValue ? GlobalConstants.GAS_CONST_G_CALL_STIPEND : 0; + final long l = (targetCalleeGas - stipend) % 63; + final long k = (targetCalleeGas - stipend - l) / 63; + checkArgument(63 * k + l + stipend == targetCalleeGas); + final long gasUpfront = getGasUpfront(transfersValue, targetAddressExists); + final long gasPreCall = (targetCalleeGas - stipend) * 64 / 63 + gasUpfront; + return preCallProgramGas + gasPreCall; // gasLimit } - private long callGasCostExcludingMemoryExpansion( - boolean transfersValue, boolean targetAddressExists, boolean isWarm) { - // GAS_CONST_G_CALL_VALUE = 9000 - // GAS_CONST_G_NEW_ACCOUNT = 25000 + long getGasUpfront(boolean transfersValue, boolean targetAddressExists) { // GAS_CONST_G_WARM_ACCESS = 100 // GAS_CONST_G_COLD_ACCOUNT_ACCESS = 2600 - return (transfersValue ? GlobalConstants.GAS_CONST_G_CALL_VALUE : 0) - + (targetAddressExists ? 0 : (transfersValue ? GlobalConstants.GAS_CONST_G_NEW_ACCOUNT : 0)) - + (isWarm - ? GlobalConstants.GAS_CONST_G_WARM_ACCESS - : GlobalConstants.GAS_CONST_G_COLD_ACCOUNT_ACCESS); + // GAS_CONST_G_CALL_VALUE = 9000 + // GAS_CONST_G_NEW_ACCOUNT = 25000 + return GAS_CONST_G_WARM_ACCESS + + (transfersValue + ? GAS_CONST_G_CALL_VALUE + (targetAddressExists ? 0 : GAS_CONST_G_NEW_ACCOUNT) + : 0); } } From 2d98a3309f7a1c3877b63a399d1e5a2dd7f42878 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Fri, 21 Feb 2025 22:33:49 +0100 Subject: [PATCH 10/16] adjusted checkArgument --- .../sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 28a55a60cb..07ea2d3cf9 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -196,9 +196,10 @@ long getGasLimit( / find gasLimit going backwards */ final long stipend = transfersValue ? GlobalConstants.GAS_CONST_G_CALL_STIPEND : 0; + checkArgument(targetCalleeGas >= stipend); final long l = (targetCalleeGas - stipend) % 63; final long k = (targetCalleeGas - stipend - l) / 63; - checkArgument(63 * k + l + stipend == targetCalleeGas); + checkArgument(targetCalleeGas == 63 * k + l + stipend); final long gasUpfront = getGasUpfront(transfersValue, targetAddressExists); final long gasPreCall = (targetCalleeGas - stipend) * 64 / 63 + gasUpfront; return preCallProgramGas + gasPreCall; // gasLimit From cfd7c193e135b0480af3f5aa538d27221d441077 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Mon, 24 Feb 2025 14:01:16 +0100 Subject: [PATCH 11/16] addressed part of review comments --- .../SixtyThreeSixtyFourthsTests.java | 29 ++++++++++--------- .../zktracer/precompiles/PrecompileUtils.java | 13 +++++---- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 07ea2d3cf9..70b05d06db 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -5,6 +5,8 @@ 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_WARM_ACCESS; import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; +import static net.consensys.linea.zktracer.opcode.OpCode.MLOAD; +import static net.consensys.linea.zktracer.opcode.OpCode.POP; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; import static org.hyperledger.besu.datatypes.Address.ALTBN128_PAIRING; @@ -68,20 +70,22 @@ public class SixtyThreeSixtyFourthsTests { Other precompiles only require a proper call data size. */ - final Bytes gas = Bytes.fromHexString("ff".repeat(32)); + final Bytes INFINITE_GAS = Bytes.fromHexString("ff".repeat(32)); + + final Bytes expandMemoryTo4096 = + BytecodeCompiler.newProgram().push(4096 - 32).op(MLOAD).op(POP).compile(); // Generic program before the final call to the precompile when the target address does not exist final BytecodeCompiler preCallTargetAddressDoesNotExistProgram = BytecodeCompiler.newProgram() - .push(4096 - 32) - .op(OpCode.MLOAD) + .immediate(expandMemoryTo4096) .push(0) // returnAtCapacity .push(0) // returnAtOffset .push(0) // callDataSize .push(0) // callDataOffset .push(0) // value .push(0) // address (this is 0 as the cost of PUSH is always the same) - .push(gas); // gas + .push(INFINITE_GAS); // gas // Cost of the generic program before the final call to the precompile when the target address // does not exist @@ -102,19 +106,18 @@ void computePreCallTargetAddressExistsProgramGasOnceForAll() { .push(0) // returnAtOffset .push(0) // callDataSize .push(0) // callDataOffset - .push(0) // value + .push(1) // value .push(address) // address - .push(gas) // gas + .push(INFINITE_GAS) // gas .op(OpCode.CALL) - .push(4096 - 32) - .op(OpCode.MLOAD) + .immediate(expandMemoryTo4096) .push(0) // returnAtCapacity .push(0) // returnAtOffset .push(0) // callDataSize .push(0) // callDataOffset .push(0) // value .push(0) // address (this is 0 as the cost of PUSH is always the same) - .push(gas); // gas + .push(INFINITE_GAS); // gas // Cost of the generic program before the final call to the precompile when the target address // exists final Function preCallTargetAddressExistsProgramGas = @@ -142,7 +145,7 @@ void sixtyThreeSixtyFourthsEcAddTest( long gasLimit, boolean insufficientGasForPrecompileExpected) { final BytecodeCompiler program = BytecodeCompiler.newProgram(); - program.push(4096 - 32).op(OpCode.MLOAD); + program.immediate(expandMemoryTo4096); program .push(0) // returnAtCapacity .push(0) // returnAtOffset @@ -150,7 +153,7 @@ void sixtyThreeSixtyFourthsEcAddTest( .push(0) // callDataOffset .push(0) // value .push(ALTBN128_ADD) // address - .push(gas) // gas + .push(INFINITE_GAS) // gas .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit); @@ -200,12 +203,12 @@ long getGasLimit( final long l = (targetCalleeGas - stipend) % 63; final long k = (targetCalleeGas - stipend - l) / 63; checkArgument(targetCalleeGas == 63 * k + l + stipend); - final long gasUpfront = getGasUpfront(transfersValue, targetAddressExists); + final long gasUpfront = getUpfrontGasCost(transfersValue, targetAddressExists); final long gasPreCall = (targetCalleeGas - stipend) * 64 / 63 + gasUpfront; return preCallProgramGas + gasPreCall; // gasLimit } - long getGasUpfront(boolean transfersValue, boolean targetAddressExists) { + long getUpfrontGasCost(boolean transfersValue, boolean targetAddressExists) { // GAS_CONST_G_WARM_ACCESS = 100 // GAS_CONST_G_COLD_ACCOUNT_ACCESS = 2600 // GAS_CONST_G_CALL_VALUE = 9000 diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java index 90f2ce9f18..1bb3d75f77 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java @@ -80,23 +80,24 @@ public static int getPrecompileCost( } } + private static int words(int sizeInBytes) { + return (sizeInBytes + WORD_SIZE_MO) / WORD_SIZE; + } + public static int getECRECCost() { return GAS_CONST_ECRECOVER; } public static int getSHA256Cost(int cds) { - final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; - return GAS_CONST_SHA2 + words * GAS_CONST_SHA2_WORD; + return GAS_CONST_SHA2 + words(cds) * GAS_CONST_SHA2_WORD; } public static int getRIPEMD160Cost(int cds) { - final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; - return GAS_CONST_RIPEMD + words * GAS_CONST_RIPEMD_WORD; + return GAS_CONST_RIPEMD + words(cds) * GAS_CONST_RIPEMD_WORD; } public static int getIDCost(int cds) { - final int words = (cds + WORD_SIZE_MO) / WORD_SIZE; - return GAS_CONST_IDENTITY + words * GAS_CONST_IDENTITY_WORD; + return GAS_CONST_IDENTITY + words(cds) * GAS_CONST_IDENTITY_WORD; } public static int getMODEXPCost(int bbs, int mbs, int exponentLog) { From 323bb9247d903266779cfb5dd04bf884a12b893f Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Mon, 24 Feb 2025 15:22:53 +0100 Subject: [PATCH 12/16] improved code quality --- .../SixtyThreeSixtyFourthsTests.java | 132 ++++++++---------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 70b05d06db..995ed85698 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -5,6 +5,7 @@ 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_WARM_ACCESS; import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; +import static net.consensys.linea.zktracer.opcode.OpCode.CALL; import static net.consensys.linea.zktracer.opcode.OpCode.MLOAD; import static net.consensys.linea.zktracer.opcode.OpCode.POP; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; @@ -20,21 +21,18 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; -import com.google.common.base.Function; import net.consensys.linea.testing.BytecodeCompiler; import net.consensys.linea.testing.BytecodeRunner; import net.consensys.linea.zktracer.module.constants.GlobalConstants; import net.consensys.linea.zktracer.module.oob.OobOperation; -import net.consensys.linea.zktracer.opcode.OpCode; import net.consensys.linea.zktracer.precompiles.PrecompileUtils; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Address; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -72,72 +70,41 @@ public class SixtyThreeSixtyFourthsTests { final Bytes INFINITE_GAS = Bytes.fromHexString("ff".repeat(32)); - final Bytes expandMemoryTo4096 = - BytecodeCompiler.newProgram().push(4096 - 32).op(MLOAD).op(POP).compile(); - - // Generic program before the final call to the precompile when the target address does not exist - final BytecodeCompiler preCallTargetAddressDoesNotExistProgram = - BytecodeCompiler.newProgram() - .immediate(expandMemoryTo4096) - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(0) // address (this is 0 as the cost of PUSH is always the same) - .push(INFINITE_GAS); // gas - // Cost of the generic program before the final call to the precompile when the target address // does not exist final long preCallTargetAddressDoesNotExistProgramGas = - BytecodeRunner.of(preCallTargetAddressDoesNotExistProgram).runOnlyForGasCost(); + BytecodeRunner.of( + BytecodeCompiler.newProgram() + .immediate(expandMemoryTo4096()) + .immediate(pushCallArguments(INFINITE_GAS, Address.ZERO, 0, false)) + .compile()) + .runOnlyForGasCost(); + // address is 0 as we are interested in the cost of the corresponding PUSH only // Cost of the generic program before the final call to the precompile when the target address - // exists - Map preCallTargetAddressExistsProgramGasMap = new HashMap<>(); - - @BeforeAll - void computePreCallTargetAddressExistsProgramGasOnceForAll() { - // Generic program before the final call to the precompile when the target address exists - final Function preCallTargetAddressExistsProgram = - (address) -> - BytecodeCompiler.newProgram() - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(1) // value - .push(address) // address - .push(INFINITE_GAS) // gas - .op(OpCode.CALL) - .immediate(expandMemoryTo4096) - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(0) // address (this is 0 as the cost of PUSH is always the same) - .push(INFINITE_GAS); // gas - // Cost of the generic program before the final call to the precompile when the target address - // exists - final Function preCallTargetAddressExistsProgramGas = - (address) -> - BytecodeRunner.of(preCallTargetAddressExistsProgram.apply(address)).runOnlyForGasCost(); - for (Address address : - List.of( - ECREC, - SHA256, - RIPEMD160, - ID, - MODEXP, - ALTBN128_ADD, - ALTBN128_MUL, - ALTBN128_PAIRING, - BLAKE2B_F_COMPRESSION)) { - preCallTargetAddressExistsProgramGasMap.put( - address, preCallTargetAddressExistsProgramGas.apply(address)); - } - } + // exists (note that for BLAKE2F nad MODEXP we need to write non-zero non-trivial inputs in + // memory) + final Map preCallTargetAddressExistsProgramGasMap = + Stream.of( + ECREC, + SHA256, + RIPEMD160, + ID, + MODEXP, + ALTBN128_ADD, + ALTBN128_MUL, + ALTBN128_PAIRING, + BLAKE2B_F_COMPRESSION) + .collect( + Collectors.toMap( + address -> address, + address -> + BytecodeRunner.of( + BytecodeCompiler.newProgram() + .immediate(call(INFINITE_GAS, address, 0, true)) + .compile()) + .runOnlyForGasCost() + + preCallTargetAddressDoesNotExistProgramGas)); @ParameterizedTest @MethodSource("sixtyThreeSixtyFourthsEcAddTestSource") @@ -145,16 +112,8 @@ void sixtyThreeSixtyFourthsEcAddTest( long gasLimit, boolean insufficientGasForPrecompileExpected) { final BytecodeCompiler program = BytecodeCompiler.newProgram(); - program.immediate(expandMemoryTo4096); - program - .push(0) // returnAtCapacity - .push(0) // returnAtOffset - .push(0) // callDataSize - .push(0) // callDataOffset - .push(0) // value - .push(ALTBN128_ADD) // address - .push(INFINITE_GAS) // gas - .op(OpCode.CALL); + program.immediate(expandMemoryTo4096()).immediate(call(INFINITE_GAS, ALTBN128_ADD, 0, false)); + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit); @@ -185,6 +144,29 @@ Stream sixtyThreeSixtyFourthsEcAddTestSource() { } // Support methods + Bytes expandMemoryTo4096() { + return BytecodeCompiler.newProgram().push(4096 - 32).op(MLOAD).op(POP).compile(); + } + + Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersValue) { + return BytecodeCompiler.newProgram() + .push(0) // returnAtCapacity + .push(0) // returnAtOffset + .push(cds) // callDataSize + .push(0) // callDataOffset + .push(transfersValue ? 1 : 0) // value + .push(address) // address + .push(gas) // gas + .compile(); + } + + Bytes call(Bytes gas, Address address, int cds, boolean transfersValue) { + return BytecodeCompiler.newProgram() + .immediate(pushCallArguments(gas, address, cds, transfersValue)) + .op(CALL) + .compile(); + } + long getGasLimit( long targetCalleeGas, boolean transfersValue, From ef613ee48491a5aaf9f616269fab403770f890a3 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Mon, 24 Feb 2025 19:59:47 +0100 Subject: [PATCH 13/16] generically treated costGEQStipendWithZeroInputTest --- .../SixtyThreeSixtyFourthsTests.java | 167 ++++++++++++++---- .../zktracer/precompiles/PrecompileUtils.java | 4 + 2 files changed, 140 insertions(+), 31 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 995ed85698..09767a9c07 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -4,6 +4,7 @@ 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_NEW_ACCOUNT; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_WARM_ACCESS; +import static net.consensys.linea.zktracer.module.constants.GlobalConstants.WORD_SIZE; import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; import static net.consensys.linea.zktracer.opcode.OpCode.CALL; import static net.consensys.linea.zktracer.opcode.OpCode.MLOAD; @@ -21,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -75,50 +77,67 @@ public class SixtyThreeSixtyFourthsTests { final long preCallTargetAddressDoesNotExistProgramGas = BytecodeRunner.of( BytecodeCompiler.newProgram() - .immediate(expandMemoryTo4096()) + .immediate(expandMemoryTo1024Words()) .immediate(pushCallArguments(INFINITE_GAS, Address.ZERO, 0, false)) .compile()) .runOnlyForGasCost(); // address is 0 as we are interested in the cost of the corresponding PUSH only + final Map cdsEnsuringCostGEQStipendMap = + new HashMap<>() { + { + put(ECREC, 0); + put(SHA256, 1024 * WORD_SIZE); + put(RIPEMD160, 1024 * WORD_SIZE); + put(ID, 1024 * WORD_SIZE); + put(MODEXP, 0); // TODO: adjust this value as needed + put(ALTBN128_ADD, 0); + put(ALTBN128_MUL, 0); + put(ALTBN128_PAIRING, 0); + put(BLAKE2B_F_COMPRESSION, 0); // TODO: adjust this value as needed + } + }; + // Cost of the generic program before the final call to the precompile when the target address // exists (note that for BLAKE2F nad MODEXP we need to write non-zero non-trivial inputs in // memory) final Map preCallTargetAddressExistsProgramGasMap = - Stream.of( - ECREC, - SHA256, - RIPEMD160, - ID, - MODEXP, - ALTBN128_ADD, - ALTBN128_MUL, - ALTBN128_PAIRING, - BLAKE2B_F_COMPRESSION) + cdsEnsuringCostGEQStipendMap.keySet().stream() .collect( Collectors.toMap( address -> address, address -> BytecodeRunner.of( - BytecodeCompiler.newProgram() - .immediate(call(INFINITE_GAS, address, 0, true)) - .compile()) - .runOnlyForGasCost() - + preCallTargetAddressDoesNotExistProgramGas)); + BytecodeCompiler.newProgram() + .immediate(expandMemoryTo1024Words()) + .immediate(call(INFINITE_GAS, address, 0, true)) + .immediate( + pushCallArguments(INFINITE_GAS, Address.ZERO, 0, false)) + .compile()) + .runOnlyForGasCost())); + + // address, csd are 0 and transferValue is false as we are interested in the cost of the + // corresponding PUSHes only @ParameterizedTest - @MethodSource("sixtyThreeSixtyFourthsEcAddTestSource") - void sixtyThreeSixtyFourthsEcAddTest( - long gasLimit, boolean insufficientGasForPrecompileExpected) { + @MethodSource("fixedCostLTStipendTestSource") + void fixedCostLTStipendTest(long gasLimit, boolean insufficientGasForPrecompileExpected) { + // Only ECADD falls in this scenario and whenever transferValue = true, gas is enough + // Thus, we only test the case in which transferValue = false + final BytecodeCompiler program = BytecodeCompiler.newProgram(); - program.immediate(expandMemoryTo4096()).immediate(call(INFINITE_GAS, ALTBN128_ADD, 0, false)); + program + .immediate(expandMemoryTo1024Words()) + .immediate(call(INFINITE_GAS, ALTBN128_ADD, 0, false)); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit); - // insufficientGasForPrecompileExpected = true => targetCalleeGas = 63/64 * (252 - 100) = 150 - // insufficientGasForPrecompileExpected = false => targetCalleeGas = 63/64 * (251 - 100) = 149 + // insufficientGasForPrecompileExpected = true => + // targetCalleeGas = 63/64 * (252 - 100) = 150 + // insufficientGasForPrecompileExpected = false => + // targetCalleeGas = 63/64 * (251 - 100) = 149 assertNotEquals( OUT_OF_GAS_EXCEPTION, @@ -131,21 +150,107 @@ void sixtyThreeSixtyFourthsEcAddTest( assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual); } - Stream sixtyThreeSixtyFourthsEcAddTestSource() { + Stream fixedCostLTStipendTestSource() { List arguments = new ArrayList<>(); final long targetCalleeGas = PrecompileUtils.getECADDCost(); - final long gasLimitEnough = - getGasLimit(targetCalleeGas, false, true, preCallTargetAddressDoesNotExistProgramGas); - final long gasLimitNotEnough = - getGasLimit(targetCalleeGas - 1, false, true, preCallTargetAddressDoesNotExistProgramGas); - arguments.add(Arguments.of(gasLimitEnough, false)); - arguments.add(Arguments.of(gasLimitNotEnough, true)); + for (int cornerCase : List.of(0, -1)) { + final long gasLimit = + getGasLimit( + targetCalleeGas + cornerCase, + false, + false, + preCallTargetAddressDoesNotExistProgramGas); + arguments.add(Arguments.of(gasLimit, cornerCase == -1)); + } + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("costGEQStipendWithZeroInputTest") + void costGEQStipendWithZeroInputTest( + Address address, + long gasLimit, + boolean insufficientGasForPrecompileExpected, + boolean transfersValue, + boolean targetAddressExists, + int cds) { + final BytecodeCompiler program = BytecodeCompiler.newProgram(); + // ECREC, SHA256, RIPEMD160, ID, ALTBN128_MUL and ALTBN128_PAIRING fall in this scenario + + program + .immediate(expandMemoryTo1024Words()) + .immediate(targetAddressExists ? call(INFINITE_GAS, address, 0, true) : Bytes.EMPTY) + .immediate(call(INFINITE_GAS, address, cds, transfersValue)); + + final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); + bytecodeRunner.run(gasLimit); + + // ECMUL case: + + // transferValue = false, targetAddressExists = false + + // insufficientGasForPrecompileExpected = true => + // targetCalleeGas = 63/64 * (6195 - 100) = 6000 + // insufficientGasForPrecompileExpected = false => + // targetCalleeGas = 63/64 * (6194 - 100) = 5999 + + // transfersValue = true, targetAddressExists = false + + // insufficientGasForPrecompileExpected = true => + // targetCalleeGas = 63/64 * (37858 - (100 + 9000 + 25000)) + 2300 = 6000 + // insufficientGasForPrecompileExpected = false => + // targetCalleeGas = 63/64 * (37857 - (100 + 9000 + 25000)) + 2300 = 5999 + + // transfersValue = true, targetAddressExists = true + + // insufficientGasForPrecompileExpected = true => + // targetCalleeGas = 63/64 * (12858 - (100 + 9000)) + 2300 = 6000 + // insufficientGasForPrecompileExpected = false => + // targetCalleeGas = 63/64 * (12857 - (100 + 9000)) + 2300 = 5999 + + assertNotEquals( + OUT_OF_GAS_EXCEPTION, + bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); + + final boolean insufficientGasForPrecompileActual = + bytecodeRunner.getHub().oob().operations().stream() + .anyMatch(OobOperation::isInsufficientGasForPrecompile); + + assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual); + } + + Stream costGEQStipendWithZeroInputTest() { + List arguments = new ArrayList<>(); + for (Address address : List.of(ECREC, SHA256, RIPEMD160, ID, ALTBN128_MUL, ALTBN128_PAIRING)) { + final int cds = cdsEnsuringCostGEQStipendMap.get(address); + final long targetCalleeGas = PrecompileUtils.getPrecompileCost(address, cds); + for (int cornerCase : List.of(0, -1)) { + for (boolean transfersValue : List.of(true, false)) { + for (boolean targetAddressExists : List.of(true, false)) { + if (!transfersValue && targetAddressExists) { + continue; // no need to test this case + } + final long gasLimit = + getGasLimit( + targetCalleeGas + cornerCase, + transfersValue, + targetAddressExists, + targetAddressExists + ? preCallTargetAddressExistsProgramGasMap.get(address) + : preCallTargetAddressDoesNotExistProgramGas); + arguments.add( + Arguments.of( + address, gasLimit, cornerCase == -1, transfersValue, targetAddressExists, cds)); + } + } + } + } return arguments.stream(); } // Support methods - Bytes expandMemoryTo4096() { - return BytecodeCompiler.newProgram().push(4096 - 32).op(MLOAD).op(POP).compile(); + Bytes expandMemoryTo1024Words() { + return BytecodeCompiler.newProgram().push(1024 * WORD_SIZE).op(MLOAD).op(POP).compile(); } Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersValue) { diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java index 1bb3d75f77..a1bbb4aa83 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java @@ -80,6 +80,10 @@ public static int getPrecompileCost( } } + public static int getPrecompileCost(Address precompileAddress, int cds) { + return getPrecompileCost(precompileAddress, cds, 0, 0, 0, 0); + } + private static int words(int sizeInBytes) { return (sizeInBytes + WORD_SIZE_MO) / WORD_SIZE; } From db0fd130157d0e1f676aeba7e410235e558619c0 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Mon, 24 Feb 2025 23:14:57 +0100 Subject: [PATCH 14/16] covered all cases --- .../SixtyThreeSixtyFourthsTests.java | 210 ++++++++++++------ .../LowGasStipendPrecompileCallTests.java | 7 +- 2 files changed, 150 insertions(+), 67 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 09767a9c07..2139e25b30 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -4,10 +4,12 @@ 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_NEW_ACCOUNT; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_WARM_ACCESS; +import static net.consensys.linea.zktracer.module.constants.GlobalConstants.PRC_BLAKE2F_SIZE; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.WORD_SIZE; import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; import static net.consensys.linea.zktracer.opcode.OpCode.CALL; import static net.consensys.linea.zktracer.opcode.OpCode.MLOAD; +import static net.consensys.linea.zktracer.opcode.OpCode.MSTORE8; import static net.consensys.linea.zktracer.opcode.OpCode.POP; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; @@ -30,11 +32,15 @@ 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.module.oob.OobOperation; +import net.consensys.linea.zktracer.opcode.OpCode; import net.consensys.linea.zktracer.precompiles.PrecompileUtils; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -67,57 +73,55 @@ public class SixtyThreeSixtyFourthsTests { * value = 1, targetAddressExists = true Note: BLAKE2F and MODEXP requires a non-zero non-trivial input to have a cost greater than 2300. - Other precompiles only require a proper call data size. + Other precompiles only require a proper call data size at most. */ final Bytes INFINITE_GAS = Bytes.fromHexString("ff".repeat(32)); - // Cost of the generic program before the final call to the precompile when the target address - // does not exist - final long preCallTargetAddressDoesNotExistProgramGas = - BytecodeRunner.of( - BytecodeCompiler.newProgram() - .immediate(expandMemoryTo1024Words()) - .immediate(pushCallArguments(INFINITE_GAS, Address.ZERO, 0, false)) - .compile()) - .runOnlyForGasCost(); - // address is 0 as we are interested in the cost of the corresponding PUSH only - - final Map cdsEnsuringCostGEQStipendMap = - new HashMap<>() { - { - put(ECREC, 0); - put(SHA256, 1024 * WORD_SIZE); - put(RIPEMD160, 1024 * WORD_SIZE); - put(ID, 1024 * WORD_SIZE); - put(MODEXP, 0); // TODO: adjust this value as needed - put(ALTBN128_ADD, 0); - put(ALTBN128_MUL, 0); - put(ALTBN128_PAIRING, 0); - put(BLAKE2B_F_COMPRESSION, 0); // TODO: adjust this value as needed - } - }; - - // Cost of the generic program before the final call to the precompile when the target address - // exists (note that for BLAKE2F nad MODEXP we need to write non-zero non-trivial inputs in - // memory) - final Map preCallTargetAddressExistsProgramGasMap = - cdsEnsuringCostGEQStipendMap.keySet().stream() + // BLAKE2F specific parameters + final int rLeadingByte = 0x09; + final int r = rLeadingByte << 8; + + // MODEXP specific parameters + int bbs = 2; + int ebs = 6; + int mbs = 128; + int exponentLog = 0; + List additionalAccounts = new ArrayList<>(); + Address codeOwnerAddress = Address.fromHexString("0xC0DE"); + + // Cost of preCallProgram in different scenarios: + // (address, transfersValue) -> gasCost + final Map> preCallProgramGasMap = + Stream.of( + ECREC, + SHA256, + RIPEMD160, + ID, + MODEXP, + ALTBN128_ADD, + ALTBN128_MUL, + ALTBN128_PAIRING, + BLAKE2B_F_COMPRESSION) .collect( Collectors.toMap( address -> address, address -> - BytecodeRunner.of( - BytecodeCompiler.newProgram() - .immediate(expandMemoryTo1024Words()) - .immediate(call(INFINITE_GAS, address, 0, true)) - .immediate( - pushCallArguments(INFINITE_GAS, Address.ZERO, 0, false)) - .compile()) - .runOnlyForGasCost())); - - // address, csd are 0 and transferValue is false as we are interested in the cost of the - // corresponding PUSHes only + new HashMap<>() { + { + put( + false, + BytecodeRunner.of(preCallProgram(address, false, false, 0)) + .runOnlyForGasCost(additionalAccounts)); + put( + true, + BytecodeRunner.of(preCallProgram(address, false, true, 0)) + .runOnlyForGasCost(additionalAccounts)); + } + })); + + // Note: transferValue = false and cds = 0 as we are interested only in the cost of the + // corresponding PUSHes here @ParameterizedTest @MethodSource("fixedCostLTStipendTestSource") @@ -127,9 +131,7 @@ void fixedCostLTStipendTest(long gasLimit, boolean insufficientGasForPrecompileE final BytecodeCompiler program = BytecodeCompiler.newProgram(); - program - .immediate(expandMemoryTo1024Words()) - .immediate(call(INFINITE_GAS, ALTBN128_ADD, 0, false)); + program.immediate(preCallProgram(ALTBN128_ADD, false, false, 0)).op(CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit); @@ -159,15 +161,15 @@ Stream fixedCostLTStipendTestSource() { targetCalleeGas + cornerCase, false, false, - preCallTargetAddressDoesNotExistProgramGas); + preCallProgramGasMap.get(ALTBN128_ADD).get(false)); arguments.add(Arguments.of(gasLimit, cornerCase == -1)); } return arguments.stream(); } @ParameterizedTest - @MethodSource("costGEQStipendWithZeroInputTest") - void costGEQStipendWithZeroInputTest( + @MethodSource("costGEQStipendTest") + void costGEQStipendTest( Address address, long gasLimit, boolean insufficientGasForPrecompileExpected, @@ -177,13 +179,10 @@ void costGEQStipendWithZeroInputTest( final BytecodeCompiler program = BytecodeCompiler.newProgram(); // ECREC, SHA256, RIPEMD160, ID, ALTBN128_MUL and ALTBN128_PAIRING fall in this scenario - program - .immediate(expandMemoryTo1024Words()) - .immediate(targetAddressExists ? call(INFINITE_GAS, address, 0, true) : Bytes.EMPTY) - .immediate(call(INFINITE_GAS, address, cds, transfersValue)); + program.immediate(preCallProgram(address, transfersValue, targetAddressExists, cds)).op(CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - bytecodeRunner.run(gasLimit); + bytecodeRunner.run(gasLimit, additionalAccounts); // ECMUL case: @@ -219,11 +218,25 @@ void costGEQStipendWithZeroInputTest( assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual); } - Stream costGEQStipendWithZeroInputTest() { + Stream costGEQStipendTest() { List arguments = new ArrayList<>(); - for (Address address : List.of(ECREC, SHA256, RIPEMD160, ID, ALTBN128_MUL, ALTBN128_PAIRING)) { - final int cds = cdsEnsuringCostGEQStipendMap.get(address); - final long targetCalleeGas = PrecompileUtils.getPrecompileCost(address, cds); + for (Address address : + List.of( + ECREC, + SHA256, + RIPEMD160, + ID, + MODEXP, + ALTBN128_MUL, + ALTBN128_PAIRING, + BLAKE2B_F_COMPRESSION)) { + final int cds = getCallDataSize(address); + final long targetCalleeGas = + address == BLAKE2B_F_COMPRESSION + ? PrecompileUtils.getBLAKE2FCost(r) + : address == MODEXP + ? PrecompileUtils.getMODEXPCost(bbs, mbs, exponentLog) + : PrecompileUtils.getPrecompileCost(address, cds); for (int cornerCase : List.of(0, -1)) { for (boolean transfersValue : List.of(true, false)) { for (boolean targetAddressExists : List.of(true, false)) { @@ -235,9 +248,7 @@ Stream costGEQStipendWithZeroInputTest() { targetCalleeGas + cornerCase, transfersValue, targetAddressExists, - targetAddressExists - ? preCallTargetAddressExistsProgramGasMap.get(address) - : preCallTargetAddressDoesNotExistProgramGas); + preCallProgramGasMap.get(address).get(targetAddressExists)); arguments.add( Arguments.of( address, gasLimit, cornerCase == -1, transfersValue, targetAddressExists, cds)); @@ -249,10 +260,37 @@ Stream costGEQStipendWithZeroInputTest() { } // Support methods + Bytes preCallProgram( + Address address, boolean transfersValue, boolean targetAddressExists, int cds) { + return BytecodeCompiler.newProgram() + .immediate(expandMemoryTo1024Words()) + .immediate( + targetAddressExists + ? call( + INFINITE_GAS, + address, + address == BLAKE2B_F_COMPRESSION + ? PRC_BLAKE2F_SIZE + : 0, // For BLAKE2F we need a meaningful cds for the call to succeed + true) + : Bytes.EMPTY) + .immediate(address == MODEXP ? storeModexpInput(bbs, mbs, ebs) : Bytes.EMPTY) + .immediate(address == BLAKE2B_F_COMPRESSION ? storeBlake2fInput(rLeadingByte) : Bytes.EMPTY) + .immediate(pushCallArguments(INFINITE_GAS, address, cds, transfersValue)) + .compile(); + } + Bytes expandMemoryTo1024Words() { return BytecodeCompiler.newProgram().push(1024 * WORD_SIZE).op(MLOAD).op(POP).compile(); } + Bytes call(Bytes gas, Address address, int cds, boolean transfersValue) { + return BytecodeCompiler.newProgram() + .immediate(pushCallArguments(gas, address, cds, transfersValue)) + .op(CALL) + .compile(); + } + Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersValue) { return BytecodeCompiler.newProgram() .push(0) // returnAtCapacity @@ -265,10 +303,42 @@ Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersVa .compile(); } - Bytes call(Bytes gas, Address address, int cds, boolean transfersValue) { + // TODO: the two methods below are essentially duplicates of the ones in + // LowGasStipendPrecompileCallTests. Consider unifying them in a common class. + Bytes storeBlake2fInput(int rLeadingByte) { + return BytecodeCompiler.newProgram().push(rLeadingByte).push(2).op(MSTORE8).compile(); + } + + private Bytes storeModexpInput(int bbs, int mbs, int ebs) { + final Bytes32 bbsPadded = Bytes32.leftPad(Bytes.of(bbs)); + final Bytes32 ebsPadded = Bytes32.leftPad(Bytes.of(ebs)); + final Bytes32 mbsPadded = Bytes32.leftPad(Bytes.of(mbs)); + final Bytes bem = + Bytes.fromHexString("0x" + "aa".repeat(bbs) + "ff".repeat(ebs) + "bb".repeat(mbs)); + final Bytes modexpInput = Bytes.concatenate(bbsPadded, ebsPadded, mbsPadded, bem); + + // codeOwnerAccount owns the bytecode that will be given as input to MODEXP through EXTCODECOPY + final ToyAccount codeOwnerAccount = + ToyAccount.builder() + .balance(Wei.of(0)) + .nonce(1) + .address(codeOwnerAddress) + .code(modexpInput) + .build(); + additionalAccounts = List.of(codeOwnerAccount); + + // This is computed here for convenience, and it is used for pricing the MODEXP precompile + exponentLog = + Math.max(OobOperation.computeExponentLog(modexpInput, 96 + bbs + ebs + mbs, bbs, ebs), 1); + + // Copy to targetOffset the code of codeOwnerAccount return BytecodeCompiler.newProgram() - .immediate(pushCallArguments(gas, address, cds, transfersValue)) - .op(CALL) + .push(codeOwnerAddress) + .op(OpCode.EXTCODESIZE) // size + .push(0) // offset + .push(0) // targetOffset + .push(codeOwnerAddress) // address + .op(OpCode.EXTCODECOPY) .compile(); } @@ -305,4 +375,16 @@ long getUpfrontGasCost(boolean transfersValue, boolean targetAddressExists) { ? GAS_CONST_G_CALL_VALUE + (targetAddressExists ? 0 : GAS_CONST_G_NEW_ACCOUNT) : 0); } + + int getCallDataSize(Address address) { + if (address == SHA256 || address == RIPEMD160 || address == ID) { + return 1024 * WORD_SIZE; // Ensures cost is greater than stipend + } else if (address == MODEXP) { + return 96 + bbs + ebs + mbs; // Ensures cost is greater than stipend with non-zero non-trivial + } else if (address == BLAKE2B_F_COMPRESSION) { + return PRC_BLAKE2F_SIZE; // Ensures cost is greater than stipend with non-zero non-trivial input + } else { + return 0; + } + } } diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java index 9a875e61a8..67437513ea 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java @@ -145,7 +145,7 @@ void lowGasStipendPrecompileCallTest( ebs = modexpCostGT200OrBlake2fRoundsGT0 ? 6 : 3; mbs = modexpCostGT200OrBlake2fRoundsGT0 ? 25 : 4; callDataSize = 96 + bbs + ebs + mbs; - prepareModexp(bbs, mbs, ebs, callDataOffset, callDataSize, program); + prepareModexp(program, bbs, mbs, ebs, callDataOffset); } else { // ECADD, ECMUL, ECRECOVER cases callDataSize = 1; // This is an arbitrary value @@ -258,7 +258,7 @@ private void prepareSha256Ripemd160Id(BytecodeCompiler program, int nWords, int } private void prepareModexp( - int bbs, int mbs, int ebs, int targetOffset, int callDataSize, BytecodeCompiler program) { + BytecodeCompiler program, int bbs, int mbs, int ebs, int targetOffset) { final Bytes32 bbsPadded = Bytes32.leftPad(Bytes.of(bbs)); final Bytes32 ebsPadded = Bytes32.leftPad(Bytes.of(ebs)); final Bytes32 mbsPadded = Bytes32.leftPad(Bytes.of(mbs)); @@ -277,7 +277,8 @@ private void prepareModexp( additionalAccounts = List.of(codeOwnerAccount); // This is computed here for convenience, and it is used for pricing the MODEXP precompile - exponentLog = Math.max(OobOperation.computeExponentLog(modexpInput, callDataSize, bbs, ebs), 1); + exponentLog = + Math.max(OobOperation.computeExponentLog(modexpInput, 96 + bbs + ebs + mbs, bbs, ebs), 1); // Copy to targetOffset the code of codeOwnerAccount program From 44144e2ad2f0f5d6541c110914a0e0c1acf75a52 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Tue, 25 Feb 2025 11:27:39 +0100 Subject: [PATCH 15/16] addressed part of review comments --- .../SixtyThreeSixtyFourthsTests.java | 129 ++++++++++-------- 1 file changed, 72 insertions(+), 57 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 2139e25b30..56594cd666 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -1,3 +1,18 @@ +/* + * Copyright Consensys Software 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.instructionprocessing.callTests.sixtyThreeSixtyFourths; import static com.google.common.base.Preconditions.checkArgument; @@ -83,10 +98,10 @@ public class SixtyThreeSixtyFourthsTests { final int r = rLeadingByte << 8; // MODEXP specific parameters - int bbs = 2; - int ebs = 6; - int mbs = 128; - int exponentLog = 0; + final int bbs = 2; + final int ebs = 6; + final int mbs = 128; + int exponentLog; // If necessary, computed in storeModexpInput List additionalAccounts = new ArrayList<>(); Address codeOwnerAddress = Address.fromHexString("0xC0DE"); @@ -123,11 +138,19 @@ public class SixtyThreeSixtyFourthsTests { // Note: transferValue = false and cds = 0 as we are interested only in the cost of the // corresponding PUSHes here + /** + * Parameterized test for the ECADD precompile, that has a fixed cost of 150. + * + * @param gasLimit the gas limit for the transaction. It is either as much as needed for the ECADD + * call or slightly less. + * @param insufficientGasForPrecompileExpected flag indicating if insufficient gas for ECADD is + * expected. + */ @ParameterizedTest - @MethodSource("fixedCostLTStipendTestSource") - void fixedCostLTStipendTest(long gasLimit, boolean insufficientGasForPrecompileExpected) { - // Only ECADD falls in this scenario and whenever transferValue = true, gas is enough - // Thus, we only test the case in which transferValue = false + @MethodSource("fixedCostEcAddTestSource") + void fixedCostEcAddTest(long gasLimit, boolean insufficientGasForPrecompileExpected) { + // Whenever transferValue = true, gas is enough + // so we only test the case in which transferValue = false final BytecodeCompiler program = BytecodeCompiler.newProgram(); @@ -136,11 +159,6 @@ void fixedCostLTStipendTest(long gasLimit, boolean insufficientGasForPrecompileE final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit); - // insufficientGasForPrecompileExpected = true => - // targetCalleeGas = 63/64 * (252 - 100) = 150 - // insufficientGasForPrecompileExpected = false => - // targetCalleeGas = 63/64 * (251 - 100) = 149 - assertNotEquals( OUT_OF_GAS_EXCEPTION, bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); @@ -152,7 +170,7 @@ void fixedCostLTStipendTest(long gasLimit, boolean insufficientGasForPrecompileE assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual); } - Stream fixedCostLTStipendTestSource() { + Stream fixedCostEcAddTestSource() { List arguments = new ArrayList<>(); final long targetCalleeGas = PrecompileUtils.getECADDCost(); for (int cornerCase : List.of(0, -1)) { @@ -167,6 +185,20 @@ Stream fixedCostLTStipendTestSource() { return arguments.stream(); } + /** + * Parameterized test for precompile calls where the cost is greater than or equal to the stipend + * (every precompile except ECADD, as long as cds and inputs are properly selected). + * + * @param address the address of the precompile contract. + * @param gasLimit the gas limit for the transaction. It is either as much as needed for the + * precompile call or slightly less. + * @param insufficientGasForPrecompileExpected flag indicating if insufficient gas for precompile + * is expected. + * @param transfersValue flag indicating if the call to the precompile transfers value. + * @param targetAddressExists flag indicating if the precompile target address exists at the + * moment of the final call. + * @param cds the call data size. + */ @ParameterizedTest @MethodSource("costGEQStipendTest") void costGEQStipendTest( @@ -177,36 +209,11 @@ void costGEQStipendTest( boolean targetAddressExists, int cds) { final BytecodeCompiler program = BytecodeCompiler.newProgram(); - // ECREC, SHA256, RIPEMD160, ID, ALTBN128_MUL and ALTBN128_PAIRING fall in this scenario - program.immediate(preCallProgram(address, transfersValue, targetAddressExists, cds)).op(CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); bytecodeRunner.run(gasLimit, additionalAccounts); - // ECMUL case: - - // transferValue = false, targetAddressExists = false - - // insufficientGasForPrecompileExpected = true => - // targetCalleeGas = 63/64 * (6195 - 100) = 6000 - // insufficientGasForPrecompileExpected = false => - // targetCalleeGas = 63/64 * (6194 - 100) = 5999 - - // transfersValue = true, targetAddressExists = false - - // insufficientGasForPrecompileExpected = true => - // targetCalleeGas = 63/64 * (37858 - (100 + 9000 + 25000)) + 2300 = 6000 - // insufficientGasForPrecompileExpected = false => - // targetCalleeGas = 63/64 * (37857 - (100 + 9000 + 25000)) + 2300 = 5999 - - // transfersValue = true, targetAddressExists = true - - // insufficientGasForPrecompileExpected = true => - // targetCalleeGas = 63/64 * (12858 - (100 + 9000)) + 2300 = 6000 - // insufficientGasForPrecompileExpected = false => - // targetCalleeGas = 63/64 * (12857 - (100 + 9000)) + 2300 = 5999 - assertNotEquals( OUT_OF_GAS_EXCEPTION, bytecodeRunner.getHub().previousTraceSection().commonValues.tracedException()); @@ -263,25 +270,21 @@ Stream costGEQStipendTest() { Bytes preCallProgram( Address address, boolean transfersValue, boolean targetAddressExists, int cds) { return BytecodeCompiler.newProgram() - .immediate(expandMemoryTo1024Words()) - .immediate( - targetAddressExists - ? call( - INFINITE_GAS, - address, - address == BLAKE2B_F_COMPRESSION - ? PRC_BLAKE2F_SIZE - : 0, // For BLAKE2F we need a meaningful cds for the call to succeed - true) - : Bytes.EMPTY) + .immediate(expandMemoryTo2048Words()) + .immediate(targetAddressExists ? successfullySummonIntoExistence(address) : Bytes.EMPTY) .immediate(address == MODEXP ? storeModexpInput(bbs, mbs, ebs) : Bytes.EMPTY) .immediate(address == BLAKE2B_F_COMPRESSION ? storeBlake2fInput(rLeadingByte) : Bytes.EMPTY) .immediate(pushCallArguments(INFINITE_GAS, address, cds, transfersValue)) .compile(); } - Bytes expandMemoryTo1024Words() { - return BytecodeCompiler.newProgram().push(1024 * WORD_SIZE).op(MLOAD).op(POP).compile(); + Bytes expandMemoryTo2048Words() { + return expandMemoryTo(2048); + } + + Bytes expandMemoryTo(int words) { + checkArgument(words >= 1); + return BytecodeCompiler.newProgram().push((words - 1) * WORD_SIZE).op(MLOAD).op(POP).compile(); } Bytes call(Bytes gas, Address address, int cds, boolean transfersValue) { @@ -303,6 +306,16 @@ Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersVa .compile(); } + Bytes successfullySummonIntoExistence(Address address) { + return call( + INFINITE_GAS, + address, + address == BLAKE2B_F_COMPRESSION + ? PRC_BLAKE2F_SIZE + : 0, // For BLAKE2F we need a meaningful cds for the call to succeed + true); + } + // TODO: the two methods below are essentially duplicates of the ones in // LowGasStipendPrecompileCallTests. Consider unifying them in a common class. Bytes storeBlake2fInput(int rLeadingByte) { @@ -349,9 +362,10 @@ long getGasLimit( long preCallProgramGas) { /* gasLimit = preCallProgramGasCost + gasPreCall / 63/64 * (gasPreCall - gasUpFront) + stipend = targetCalleeGas - / x = gasPreCall - gasUpFront - / k = x / 64 (integer division) - / l = x - 64 * k + / x = gasPreCall - gasUpFront = 64 * k + l + / x !≡ 63 % 64 => (x - 63) % 64 != 0 + / k = floor(x / 64) + / l = x % 64 / 63 * k + l + stipend = targetCalleeGas / find gasLimit going backwards */ @@ -361,7 +375,7 @@ long getGasLimit( final long k = (targetCalleeGas - stipend - l) / 63; checkArgument(targetCalleeGas == 63 * k + l + stipend); final long gasUpfront = getUpfrontGasCost(transfersValue, targetAddressExists); - final long gasPreCall = (targetCalleeGas - stipend) * 64 / 63 + gasUpfront; + final long gasPreCall = 64 * k + l + gasUpfront; return preCallProgramGas + gasPreCall; // gasLimit } @@ -382,7 +396,8 @@ int getCallDataSize(Address address) { } else if (address == MODEXP) { return 96 + bbs + ebs + mbs; // Ensures cost is greater than stipend with non-zero non-trivial } else if (address == BLAKE2B_F_COMPRESSION) { - return PRC_BLAKE2F_SIZE; // Ensures cost is greater than stipend with non-zero non-trivial input + return PRC_BLAKE2F_SIZE; // Ensures cost is greater than stipend with non-zero non-trivial + // input } else { return 0; } From 15916643ba92bf5d3cb9a4a755fd919fe8bfc797 Mon Sep 17 00:00:00 2001 From: Lorenzo Gentile Date: Tue, 25 Feb 2025 13:40:00 +0100 Subject: [PATCH 16/16] addressed all review comments and unified support methods for precompile testing --- .../SixtyThreeSixtyFourthsTests.java | 137 +++++++++--------- .../LowGasStipendPrecompileCallTests.java | 116 ++++----------- .../zktracer/precompiles/PrecompileUtils.java | 90 ++++++++++++ 3 files changed, 186 insertions(+), 157 deletions(-) diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java index 56594cd666..c5adaea02e 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/instructionprocessing/callTests/sixtyThreeSixtyFourths/SixtyThreeSixtyFourthsTests.java @@ -24,8 +24,14 @@ import static net.consensys.linea.zktracer.module.hub.signals.TracedException.OUT_OF_GAS_EXCEPTION; import static net.consensys.linea.zktracer.opcode.OpCode.CALL; import static net.consensys.linea.zktracer.opcode.OpCode.MLOAD; -import static net.consensys.linea.zktracer.opcode.OpCode.MSTORE8; import static net.consensys.linea.zktracer.opcode.OpCode.POP; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.generateModexpInput; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getBLAKE2FCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getECADDCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getMODEXPCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getPrecompileCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.prepareBlake2F; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.prepareModexp; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; import static org.hyperledger.besu.datatypes.Address.ALTBN128_PAIRING; @@ -50,10 +56,7 @@ import net.consensys.linea.testing.ToyAccount; import net.consensys.linea.zktracer.module.constants.GlobalConstants; import net.consensys.linea.zktracer.module.oob.OobOperation; -import net.consensys.linea.zktracer.opcode.OpCode; -import net.consensys.linea.zktracer.precompiles.PrecompileUtils; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.junit.jupiter.api.TestInstance; @@ -89,6 +92,8 @@ public class SixtyThreeSixtyFourthsTests { Note: BLAKE2F and MODEXP requires a non-zero non-trivial input to have a cost greater than 2300. Other precompiles only require a proper call data size at most. + + See https://github.com/Consensys/linea-tracer/issues/1153 for additional documentation. */ final Bytes INFINITE_GAS = Bytes.fromHexString("ff".repeat(32)); @@ -101,9 +106,19 @@ public class SixtyThreeSixtyFourthsTests { final int bbs = 2; final int ebs = 6; final int mbs = 128; - int exponentLog; // If necessary, computed in storeModexpInput - List additionalAccounts = new ArrayList<>(); - Address codeOwnerAddress = Address.fromHexString("0xC0DE"); + final Bytes modexpInput = generateModexpInput(bbs, mbs, ebs); + final int exponentLog = + OobOperation.computeExponentLog(modexpInput, 96 + bbs + ebs + mbs, bbs, ebs); + final Address codeOwnerAddress = Address.fromHexString("0xC0DE"); + // codeOwnerAccount owns the bytecode that will be given as input to MODEXP through EXTCODECOPY + final ToyAccount codeOwnerAccount = + ToyAccount.builder() + .balance(Wei.of(0)) + .nonce(1) + .address(codeOwnerAddress) + .code(modexpInput) + .build(); + final List additionalAccounts = List.of(codeOwnerAccount); // Cost of preCallProgram in different scenarios: // (address, transfersValue) -> gasCost @@ -127,11 +142,13 @@ public class SixtyThreeSixtyFourthsTests { put( false, BytecodeRunner.of(preCallProgram(address, false, false, 0)) - .runOnlyForGasCost(additionalAccounts)); + .runOnlyForGasCost( + address == MODEXP ? additionalAccounts : List.of())); put( true, BytecodeRunner.of(preCallProgram(address, false, true, 0)) - .runOnlyForGasCost(additionalAccounts)); + .runOnlyForGasCost( + address == MODEXP ? additionalAccounts : List.of())); } })); @@ -172,7 +189,7 @@ void fixedCostEcAddTest(long gasLimit, boolean insufficientGasForPrecompileExpec Stream fixedCostEcAddTestSource() { List arguments = new ArrayList<>(); - final long targetCalleeGas = PrecompileUtils.getECADDCost(); + final long targetCalleeGas = getECADDCost(); for (int cornerCase : List.of(0, -1)) { final long gasLimit = getGasLimit( @@ -193,7 +210,7 @@ Stream fixedCostEcAddTestSource() { * @param gasLimit the gas limit for the transaction. It is either as much as needed for the * precompile call or slightly less. * @param insufficientGasForPrecompileExpected flag indicating if insufficient gas for precompile - * is expected. + * call is expected. * @param transfersValue flag indicating if the call to the precompile transfers value. * @param targetAddressExists flag indicating if the precompile target address exists at the * moment of the final call. @@ -212,7 +229,7 @@ void costGEQStipendTest( program.immediate(preCallProgram(address, transfersValue, targetAddressExists, cds)).op(CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - bytecodeRunner.run(gasLimit, additionalAccounts); + bytecodeRunner.run(gasLimit, address == MODEXP ? additionalAccounts : List.of()); assertNotEquals( OUT_OF_GAS_EXCEPTION, @@ -240,10 +257,10 @@ Stream costGEQStipendTest() { final int cds = getCallDataSize(address); final long targetCalleeGas = address == BLAKE2B_F_COMPRESSION - ? PrecompileUtils.getBLAKE2FCost(r) + ? getBLAKE2FCost(r) : address == MODEXP - ? PrecompileUtils.getMODEXPCost(bbs, mbs, exponentLog) - : PrecompileUtils.getPrecompileCost(address, cds); + ? getMODEXPCost(bbs, mbs, exponentLog) + : getPrecompileCost(address, cds); for (int cornerCase : List.of(0, -1)) { for (boolean transfersValue : List.of(true, false)) { for (boolean targetAddressExists : List.of(true, false)) { @@ -272,8 +289,9 @@ Bytes preCallProgram( return BytecodeCompiler.newProgram() .immediate(expandMemoryTo2048Words()) .immediate(targetAddressExists ? successfullySummonIntoExistence(address) : Bytes.EMPTY) - .immediate(address == MODEXP ? storeModexpInput(bbs, mbs, ebs) : Bytes.EMPTY) - .immediate(address == BLAKE2B_F_COMPRESSION ? storeBlake2fInput(rLeadingByte) : Bytes.EMPTY) + .immediate( + address == MODEXP ? prepareModexp(modexpInput, 0, codeOwnerAddress) : Bytes.EMPTY) + .immediate(address == BLAKE2B_F_COMPRESSION ? prepareBlake2F(rLeadingByte, 2) : Bytes.EMPTY) .immediate(pushCallArguments(INFINITE_GAS, address, cds, transfersValue)) .compile(); } @@ -287,6 +305,16 @@ Bytes expandMemoryTo(int words) { return BytecodeCompiler.newProgram().push((words - 1) * WORD_SIZE).op(MLOAD).op(POP).compile(); } + Bytes successfullySummonIntoExistence(Address address) { + return call( + INFINITE_GAS, + address, + address == BLAKE2B_F_COMPRESSION + ? PRC_BLAKE2F_SIZE + : 0, // For BLAKE2F we need a meaningful cds for the call to succeed + true); + } + Bytes call(Bytes gas, Address address, int cds, boolean transfersValue) { return BytecodeCompiler.newProgram() .immediate(pushCallArguments(gas, address, cds, transfersValue)) @@ -306,68 +334,30 @@ Bytes pushCallArguments(Bytes gas, Address address, int cds, boolean transfersVa .compile(); } - Bytes successfullySummonIntoExistence(Address address) { - return call( - INFINITE_GAS, - address, - address == BLAKE2B_F_COMPRESSION - ? PRC_BLAKE2F_SIZE - : 0, // For BLAKE2F we need a meaningful cds for the call to succeed - true); - } - - // TODO: the two methods below are essentially duplicates of the ones in - // LowGasStipendPrecompileCallTests. Consider unifying them in a common class. - Bytes storeBlake2fInput(int rLeadingByte) { - return BytecodeCompiler.newProgram().push(rLeadingByte).push(2).op(MSTORE8).compile(); - } - - private Bytes storeModexpInput(int bbs, int mbs, int ebs) { - final Bytes32 bbsPadded = Bytes32.leftPad(Bytes.of(bbs)); - final Bytes32 ebsPadded = Bytes32.leftPad(Bytes.of(ebs)); - final Bytes32 mbsPadded = Bytes32.leftPad(Bytes.of(mbs)); - final Bytes bem = - Bytes.fromHexString("0x" + "aa".repeat(bbs) + "ff".repeat(ebs) + "bb".repeat(mbs)); - final Bytes modexpInput = Bytes.concatenate(bbsPadded, ebsPadded, mbsPadded, bem); - - // codeOwnerAccount owns the bytecode that will be given as input to MODEXP through EXTCODECOPY - final ToyAccount codeOwnerAccount = - ToyAccount.builder() - .balance(Wei.of(0)) - .nonce(1) - .address(codeOwnerAddress) - .code(modexpInput) - .build(); - additionalAccounts = List.of(codeOwnerAccount); - - // This is computed here for convenience, and it is used for pricing the MODEXP precompile - exponentLog = - Math.max(OobOperation.computeExponentLog(modexpInput, 96 + bbs + ebs + mbs, bbs, ebs), 1); - - // Copy to targetOffset the code of codeOwnerAccount - return BytecodeCompiler.newProgram() - .push(codeOwnerAddress) - .op(OpCode.EXTCODESIZE) // size - .push(0) // offset - .push(0) // targetOffset - .push(codeOwnerAddress) // address - .op(OpCode.EXTCODECOPY) - .compile(); - } - + /** + * Calculates the gas limit for a transaction to a contract calling a callee (in this class, a + * precompile), based on targetCallGas, transfersValue, targetAddressExists, and + * preCallProgramGas. + * + * @param targetCalleeGas the gas given to the callee. + * @param transfersValue flag indicating if the call to callee transfers value. + * @param targetAddressExists flag indicating if the target address exists. + * @param preCallProgramGas the gas cost of the preCallProgram. + * @return the calculated gas limit for the transaction. + */ long getGasLimit( long targetCalleeGas, boolean transfersValue, boolean targetAddressExists, long preCallProgramGas) { /* gasLimit = preCallProgramGasCost + gasPreCall - / 63/64 * (gasPreCall - gasUpFront) + stipend = targetCalleeGas - / x = gasPreCall - gasUpFront = 64 * k + l + / let x = gasPreCall - gasUpFront = 64 * k + l + / with: / x !≡ 63 % 64 => (x - 63) % 64 != 0 / k = floor(x / 64) / l = x % 64 / 63 * k + l + stipend = targetCalleeGas - / find gasLimit going backwards + / find gasLimit going backwards */ final long stipend = transfersValue ? GlobalConstants.GAS_CONST_G_CALL_STIPEND : 0; checkArgument(targetCalleeGas >= stipend); @@ -379,6 +369,15 @@ long getGasLimit( return preCallProgramGas + gasPreCall; // gasLimit } + /** + * Calculates the upfront gas cost of a CALL-type instruction based on whether it transfers value + * and if the target address exists. It assumed target address is warm and there is no memory + * expansion. + * + * @param transfersValue flag indicating if the call transfers value. + * @param targetAddressExists flag indicating if the target address exists. + * @return the upfront gas cost for the call. + */ long getUpfrontGasCost(boolean transfersValue, boolean targetAddressExists) { // GAS_CONST_G_WARM_ACCESS = 100 // GAS_CONST_G_COLD_ACCOUNT_ACCESS = 2600 diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java index 67437513ea..54c2b1fc7e 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/LowGasStipendPrecompileCallTests.java @@ -15,12 +15,16 @@ package net.consensys.linea.zktracer.precompiles; -import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.populateMemory; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_G_CALL_STIPEND; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.PRC_BLAKE2F_SIZE; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.PRC_ECPAIRING_SIZE; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.WORD_SIZE; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.generateModexpInput; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getExpectedReturnAtCapacity; import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.getPrecompileCost; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.prepareBlake2F; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.prepareModexp; +import static net.consensys.linea.zktracer.precompiles.PrecompileUtils.prepareSha256Ripemd160Id; import static org.hyperledger.besu.datatypes.Address.ALTBN128_ADD; import static org.hyperledger.besu.datatypes.Address.ALTBN128_MUL; import static org.hyperledger.besu.datatypes.Address.ALTBN128_PAIRING; @@ -47,7 +51,6 @@ import net.consensys.linea.zktracer.module.oob.OobOperation; import net.consensys.linea.zktracer.opcode.OpCode; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.junit.jupiter.params.ParameterizedTest; @@ -83,12 +86,12 @@ enum GasCase { int r = 0; // MODEXP specific parameters - int bbs = 0; - int ebs = 0; - int mbs = 0; - List additionalAccounts = new ArrayList<>(); - Address codeOwnerAddress = Address.fromHexString("0xC0DE"); - int exponentLog = 0; + int bbs; // Defined within the test case + int ebs; // Defined within the test case + int mbs; // Defined within the test case + int exponentLog; // Computed within the test case + final Address codeOwnerAddress = Address.fromHexString("0xC0DE"); + List additionalAccounts = new ArrayList<>(); // Re-assigned within the test case /** * Parameterized test for low gas stipend precompile call. In this family of tests we call every @@ -131,7 +134,7 @@ void lowGasStipendPrecompileCallTest( rLeadingByte = modexpCostGT200OrBlake2fRoundsGT0 ? 0x12 : 0; r = rLeadingByte << 8; callDataSize = PRC_BLAKE2F_SIZE; - prepareBlake2F(program, rLeadingByte, callDataOffset); + prepareBlake2F(program, rLeadingByte, callDataOffset + 2); } else if (precompileAddress == ALTBN128_PAIRING) { callDataSize = PRC_ECPAIRING_SIZE; } else if ((precompileAddress == SHA256 @@ -145,7 +148,21 @@ void lowGasStipendPrecompileCallTest( ebs = modexpCostGT200OrBlake2fRoundsGT0 ? 6 : 3; mbs = modexpCostGT200OrBlake2fRoundsGT0 ? 25 : 4; callDataSize = 96 + bbs + ebs + mbs; - prepareModexp(program, bbs, mbs, ebs, callDataOffset); + + final Bytes modexpInput = generateModexpInput(bbs, mbs, ebs); + exponentLog = OobOperation.computeExponentLog(modexpInput, callDataSize, bbs, ebs); + // codeOwnerAccount owns the bytecode that will be given as input to MODEXP through + // EXTCODECOPY + final ToyAccount codeOwnerAccount = + ToyAccount.builder() + .balance(Wei.of(0)) + .nonce(1) + .address(codeOwnerAddress) + .code(modexpInput) + .build(); + additionalAccounts = List.of(codeOwnerAccount); + + prepareModexp(program, modexpInput, callDataOffset, codeOwnerAddress); } else { // ECADD, ECMUL, ECRECOVER cases callDataSize = 1; // This is an arbitrary value @@ -188,7 +205,7 @@ void lowGasStipendPrecompileCallTest( .push(gas) // gas .op(OpCode.CALL); final BytecodeRunner bytecodeRunner = BytecodeRunner.of(program); - bytecodeRunner.run(61_000_000L, additionalAccounts); + bytecodeRunner.run(61_000_000L, precompileAddress == MODEXP ? additionalAccounts : List.of()); final Hub hub = bytecodeRunner.getHub(); // Here we check if OOB detects the insufficient gas for the precompile call @@ -244,83 +261,6 @@ static Stream lowGasStipendPrecompileCallTestSource() { } // Support methods - private void prepareBlake2F(BytecodeCompiler program, int rLeadingByte, int callDataOffset) { - program - .push(rLeadingByte) // For simplicity, we only set the first byte of r - .push(callDataOffset + 2) // offset - // Writing rLeadingByte at this offset - // allows to have r = 0x00000000 or r = 0x00001200 - .op(OpCode.MSTORE8); - } - - private void prepareSha256Ripemd160Id(BytecodeCompiler program, int nWords, int callDataOffset) { - populateMemory(program, nWords, callDataOffset); - } - - private void prepareModexp( - BytecodeCompiler program, int bbs, int mbs, int ebs, int targetOffset) { - final Bytes32 bbsPadded = Bytes32.leftPad(Bytes.of(bbs)); - final Bytes32 ebsPadded = Bytes32.leftPad(Bytes.of(ebs)); - final Bytes32 mbsPadded = Bytes32.leftPad(Bytes.of(mbs)); - final Bytes bem = - Bytes.fromHexString("0x" + "aa".repeat(bbs) + "ff".repeat(ebs) + "bb".repeat(mbs)); - final Bytes modexpInput = Bytes.concatenate(bbsPadded, ebsPadded, mbsPadded, bem); - - // codeOwnerAccount owns the bytecode that will be given as input to MODEXP through EXTCODECOPY - final ToyAccount codeOwnerAccount = - ToyAccount.builder() - .balance(Wei.of(0)) - .nonce(1) - .address(codeOwnerAddress) - .code(modexpInput) - .build(); - additionalAccounts = List.of(codeOwnerAccount); - - // This is computed here for convenience, and it is used for pricing the MODEXP precompile - exponentLog = - Math.max(OobOperation.computeExponentLog(modexpInput, 96 + bbs + ebs + mbs, bbs, ebs), 1); - - // Copy to targetOffset the code of codeOwnerAccount - program - .push(codeOwnerAddress) - .op(OpCode.EXTCODESIZE) // size - .push(0) // offset - .push(targetOffset) // targetOffset - .push(codeOwnerAddress) // address - .op(OpCode.EXTCODECOPY); - } - - /** - * Computes the expected returnAtCapacity based on the precompile address, and callDataSize in the - * case of ID. - * - * @param precompileAddress the address of the precompile contract. - * @param callDataSize the call data size. Beyond the case of ID, this value is ignored. - * @param mbs the modulo byte size. Beyond the case of MODEXP, this value is ignored. - * @return the computed return at capacity. - */ - private static int getExpectedReturnAtCapacity( - Address precompileAddress, int callDataSize, int mbs) { - final int returnAtCapacity; - if (precompileAddress == ECREC - || precompileAddress == SHA256 - || precompileAddress == RIPEMD160 - || precompileAddress == ALTBN128_PAIRING) { - returnAtCapacity = WORD_SIZE; - } else if (precompileAddress == ALTBN128_ADD - || precompileAddress == ALTBN128_MUL - || precompileAddress == BLAKE2B_F_COMPRESSION) { - returnAtCapacity = 2 * WORD_SIZE; - } else if (precompileAddress == MODEXP) { - returnAtCapacity = mbs; - } else if (precompileAddress == ID) { - returnAtCapacity = callDataSize; - } else { - throw new IllegalArgumentException("Unknown precompile address"); - } - return returnAtCapacity; - } - /** * Computes the gas based on the gas parameter and precompile cost. * diff --git a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java index a1bbb4aa83..d81f9d3bb5 100644 --- a/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java +++ b/arithmetization/src/test/java/net/consensys/linea/zktracer/precompiles/PrecompileUtils.java @@ -15,6 +15,7 @@ package net.consensys.linea.zktracer.precompiles; +import static net.consensys.linea.zktracer.instructionprocessing.callTests.Utilities.populateMemory; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_BLAKE2_PER_ROUND; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_ECADD; import static net.consensys.linea.zktracer.module.constants.GlobalConstants.GAS_CONST_ECMUL; @@ -34,6 +35,10 @@ import static net.consensys.linea.zktracer.module.oob.Trace.G_QUADDIVISOR; import static org.hyperledger.besu.datatypes.Address.*; +import net.consensys.linea.testing.BytecodeCompiler; +import net.consensys.linea.zktracer.opcode.OpCode; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.datatypes.Address; public class PrecompileUtils { @@ -126,4 +131,89 @@ public static int getECPAIRINGCost(int cds) { public static int getBLAKE2FCost(int r) { return GAS_CONST_BLAKE2_PER_ROUND * r; } + + // Methods to prepare inputs for certain precompiles + + // BLAKE2F + static void prepareBlake2F(BytecodeCompiler program, int rLeadingByte, int offset) { + program + .push(rLeadingByte) // For simplicity, we only set the first byte of r + .push(offset) // offset + .op(OpCode.MSTORE8); + } + + public static Bytes prepareBlake2F(int rLeadingByte, int offset) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + prepareBlake2F(program, rLeadingByte, offset); + return program.compile(); + } + + // SHA256, RIPEMD160, ID + static void prepareSha256Ripemd160Id(BytecodeCompiler program, int nWords, int offset) { + populateMemory(program, nWords, offset); + } + + static Bytes prepareSha256Ripemd160Id(int nWords, int offset) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + prepareSha256Ripemd160Id(program, nWords, offset); + return program.compile(); + } + + // MODEXP + public static Bytes generateModexpInput(int bbs, int mbs, int ebs) { + final Bytes32 bbsPadded = Bytes32.leftPad(Bytes.of(bbs)); + final Bytes32 ebsPadded = Bytes32.leftPad(Bytes.of(ebs)); + final Bytes32 mbsPadded = Bytes32.leftPad(Bytes.of(mbs)); + final Bytes bem = + Bytes.fromHexString("0x" + "aa".repeat(bbs) + "ff".repeat(ebs) + "bb".repeat(mbs)); + return Bytes.concatenate(bbsPadded, ebsPadded, mbsPadded, bem); + } + + static void prepareModexp( + BytecodeCompiler program, Bytes modexpInput, int targetOffset, Address codeOwnerAddress) { + // Copy to targetOffset the code of codeOwnerAccount + program + .push(codeOwnerAddress) + .op(OpCode.EXTCODESIZE) // size + .push(0) // offset + .push(targetOffset) // targetOffset + .push(codeOwnerAddress) // address + .op(OpCode.EXTCODECOPY); + } + + public static Bytes prepareModexp(Bytes modexpInput, int targetOffset, Address codeOwnerAddress) { + BytecodeCompiler program = BytecodeCompiler.newProgram(); + prepareModexp(program, modexpInput, targetOffset, codeOwnerAddress); + return program.compile(); + } + + /** + * Computes the expected returnAtCapacity based on the precompile address, and callDataSize in the + * case of ID. + * + * @param precompileAddress the address of the precompile contract. + * @param callDataSize the call data size. Beyond the case of ID, this value is ignored. + * @param mbs the modulo byte size. Beyond the case of MODEXP, this value is ignored. + * @return the computed return at capacity. + */ + static int getExpectedReturnAtCapacity(Address precompileAddress, int callDataSize, int mbs) { + final int returnAtCapacity; + if (precompileAddress == ECREC + || precompileAddress == SHA256 + || precompileAddress == RIPEMD160 + || precompileAddress == ALTBN128_PAIRING) { + returnAtCapacity = WORD_SIZE; + } else if (precompileAddress == ALTBN128_ADD + || precompileAddress == ALTBN128_MUL + || precompileAddress == BLAKE2B_F_COMPRESSION) { + returnAtCapacity = 2 * WORD_SIZE; + } else if (precompileAddress == MODEXP) { + returnAtCapacity = mbs; + } else if (precompileAddress == ID) { + returnAtCapacity = callDataSize; + } else { + throw new IllegalArgumentException("Unknown precompile address"); + } + return returnAtCapacity; + } }