Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Precompile tests where we run up against the 63-64ths stuff #1842

Open
wants to merge 21 commits into
base: arith-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a85fc3b
added docs
lorenzogentile404 Feb 18, 2025
5269c80
added test for ECADD case
lorenzogentile404 Feb 19, 2025
df72138
Merge branch 'arith-dev' into 1153-precompile-tests-where-we-run-up-a…
lorenzogentile404 Feb 20, 2025
f862f0d
fixed ecadd test and added ecmul case
lorenzogentile404 Feb 20, 2025
e2ab2f0
set gas parameter to max possible value
lorenzogentile404 Feb 20, 2025
824152b
added ecadd test with value case
lorenzogentile404 Feb 20, 2025
6c27b19
added docs
lorenzogentile404 Feb 21, 2025
c21c066
added documentation and highlighted issue with the current tests
lorenzogentile404 Feb 21, 2025
39b4caa
added docs
lorenzogentile404 Feb 21, 2025
7bdb032
fixed approach to testing and added machinery
lorenzogentile404 Feb 21, 2025
2d98a33
adjusted checkArgument
lorenzogentile404 Feb 21, 2025
cfd7c19
addressed part of review comments
lorenzogentile404 Feb 24, 2025
8158bba
Merge branch 'arith-dev' into 1153-precompile-tests-where-we-run-up-a…
lorenzogentile404 Feb 24, 2025
323bb92
improved code quality
lorenzogentile404 Feb 24, 2025
ef613ee
generically treated costGEQStipendWithZeroInputTest
lorenzogentile404 Feb 24, 2025
db0fd13
covered all cases
lorenzogentile404 Feb 24, 2025
6880f86
Merge branch 'arith-dev' into 1153-precompile-tests-where-we-run-up-a…
lorenzogentile404 Feb 25, 2025
44144e2
addressed part of review comments
lorenzogentile404 Feb 25, 2025
1591664
addressed all review comments and unified support methods for precomp…
lorenzogentile404 Feb 25, 2025
147f900
Merge branch 'arith-dev' into 1153-precompile-tests-where-we-run-up-a…
lorenzogentile404 Feb 25, 2025
774cac3
Merge branch 'arith-dev' into 1153-precompile-tests-where-we-run-up-a…
lorenzogentile404 Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
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 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.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.
*
* 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
*/

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class SixtyThreeSixtyFourthsTests {

/*
Cases to cover:

* value = 0
If precompileGasCost >= 2300 then we are interested in:
* value = 1, targetAddressExists = false
* 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.
*/

final Bytes gas = Bytes.fromHexString("ff".repeat(32));

// 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<Address, Long> preCallTargetAddressExistsProgramGasMap = new HashMap<>();

@BeforeAll
void computePreCallTargetAddressExistsProgramGasOnceForAll() {
// Generic program before the final call to the precompile when the target address exists
final Function<Address, BytecodeCompiler> 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<Address, Long> 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));
}
}

@ParameterizedTest
@MethodSource("sixtyThreeSixtyFourthsEcAddTestSource")
void sixtyThreeSixtyFourthsEcAddTest(
long gasLimit, boolean insufficientGasForPrecompileExpected) {
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(gas) // gas
.op(OpCode.CALL);
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());

final boolean insufficientGasForPrecompileActual =
bytecodeRunner.getHub().oob().operations().stream()
.anyMatch(OobOperation::isInsufficientGasForPrecompile);

assertEquals(insufficientGasForPrecompileExpected, insufficientGasForPrecompileActual);
}

Stream<Arguments> sixtyThreeSixtyFourthsEcAddTestSource() {
List<Arguments> 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();
}

// 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;
checkArgument(targetCalleeGas >= stipend);
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 gasPreCall = (targetCalleeGas - stipend) * 64 / 63 + gasUpfront;
return preCallProgramGas + gasPreCall; // gasLimit
}

long getGasUpfront(boolean transfersValue, boolean targetAddressExists) {
// GAS_CONST_G_WARM_ACCESS = 100
// GAS_CONST_G_COLD_ACCOUNT_ACCESS = 2600
// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading