Skip to content

Commit

Permalink
Merge pull request #2885 from rsksmart/add-gas-calculation
Browse files Browse the repository at this point in the history
Add gas calculation
  • Loading branch information
Vovchyk authored Dec 10, 2024
2 parents f53a6b0 + 5d207fd commit 80838a5
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 9 deletions.
2 changes: 1 addition & 1 deletion rskj-core/src/main/java/org/ethereum/vm/OpCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public enum OpCode {
/**
* (0x5e) Memory copying instruction
*/
MCOPY(0x5e, 3, 0, BASE_TIER),
MCOPY(0x5e, 3, 0, VERY_LOW_TIER),

/* Push Operations */
/**
Expand Down
2 changes: 1 addition & 1 deletion rskj-core/src/main/java/org/ethereum/vm/OpCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private OpCodes() {
/**
* (0x5e)
*/
static final byte OP_MCOPY = 0x5e;
public static final byte OP_MCOPY = 0x5e;

/* Push Operations */
/**
Expand Down
18 changes: 11 additions & 7 deletions rskj-core/src/main/java/org/ethereum/vm/VM.java
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,16 @@ private long computeDataCopyGas() {
return calcMemGas(oldMemSize, newMemSize, copySize);
}

private long computeMemoryCopyGas() {
DataWord length = stack.get(stack.size() - 3);
DataWord offset = stack.peek();
long copySize = Program.limitToMaxLong(length);
checkSizeArgument(copySize);
long newMemSize = memNeeded(offset, copySize);
// Note: 3 additional units are added outside because of the "Very Low Tier" configuration
return calcMemGas(oldMemSize, newMemSize, copySize);
}

protected void doCODESIZE() {
if (computeGas) {
if (op == OpCode.EXTCODESIZE) {
Expand Down Expand Up @@ -1438,13 +1448,7 @@ protected void doJUMPDEST()

protected void doMCOPY() {
if (computeGas) {
// See "Gas Cost" section on EIP 5656
// gas cost = 3 * (length + 31) + memory expansion cost + very low
long length = stack.get(stack.size() - 3).longValue();
long newMemSize = memNeeded(stack.peek(), length);
long cost = 3 * (length + 31) + calcMemGas(oldMemSize, newMemSize, 0) + 3; // TODO -> Check copy size

gasCost = GasCost.add(gasCost, cost);
gasCost = GasCost.add(gasCost, computeMemoryCopyGas());
spendOpCodeGas();
}

Expand Down
98 changes: 98 additions & 0 deletions rskj-core/src/test/java/co/rsk/vm/opcode/MCopyGasTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package co.rsk.vm.opcode;

import co.rsk.config.TestSystemProperties;
import co.rsk.config.VmConfig;
import co.rsk.peg.BridgeSupportFactory;
import co.rsk.peg.RepositoryBtcBlockStoreWithCache;
import co.rsk.vm.BytecodeCompiler;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.core.*;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.PrecompiledContracts;
import org.ethereum.vm.VM;
import org.ethereum.vm.program.Program;
import org.ethereum.vm.program.invoke.ProgramInvokeMockImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.HashSet;
import java.util.stream.Stream;

import static co.rsk.net.utils.TransactionUtils.createTransaction;
import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP445;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class MCopyGasTest {

private ActivationConfig.ForBlock activationConfig;

private final ProgramInvokeMockImpl invoke = new ProgramInvokeMockImpl();
private final BytecodeCompiler compiler = new BytecodeCompiler();
private final TestSystemProperties config = new TestSystemProperties();
private final VmConfig vmConfig = config.getVmConfig();
private final SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache());
private final BlockFactory blockFactory = new BlockFactory(config.getActivationConfig());
private final Transaction transaction = createTransaction();
private final PrecompiledContracts precompiledContracts = new PrecompiledContracts(
config,
new BridgeSupportFactory(
new RepositoryBtcBlockStoreWithCache.Factory(
config.getNetworkConstants().getBridgeConstants().getBtcParams()),
config.getNetworkConstants().getBridgeConstants(),
config.getActivationConfig(), signatureCache), signatureCache);

@BeforeEach
void setup() {
activationConfig = mock(ActivationConfig.ForBlock.class);
when(activationConfig.isActive(RSKIP445)).thenReturn(true);
}

@ParameterizedTest
@MethodSource("provideParametersForMCOPYGasTest")
void testMCopy_ShouldConsumeTheCorrectAmountOfGas(String[] initMemory, int dst, int src, int length, int expectedGasUsage) {
// Given
byte[] code = compiler.compile("MCOPY");
VM vm = new VM(vmConfig, precompiledContracts);

Program program = new Program(vmConfig, precompiledContracts, blockFactory, activationConfig, code, invoke, transaction, new HashSet<>(), new BlockTxSignatureCache(new ReceivedTxSignatureCache()));

int address = 0;
for (String entry : initMemory) {
program.memorySave(DataWord.valueOf(address), DataWord.valueFromHex(entry));
address += 32;
}

program.stackPush(DataWord.valueOf(length)); // Mind the stack order!!
program.stackPush(DataWord.valueOf(src));
program.stackPush(DataWord.valueOf(dst));

// When
try {
while (!program.isStopped()) {
vm.step(program);
}
} catch(Program.StackTooSmallException e) {
Assertions.fail("Stack too small exception");
}

// Then
Assertions.assertEquals(0, program.getStack().size());
Assertions.assertEquals(expectedGasUsage, program.getResult().getGasUsed());
}

private static Stream<Arguments> provideParametersForMCOPYGasTest() {
return Stream.of(
Arguments.of(new String[]{ "0000000000000000000000000000000000000000000000000000000000000000", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" }, 0, 32, 32, 6),
Arguments.of(new String[]{ "0101010101010101010101010101010101010101010101010101010101010101" }, 0, 0, 32, 6),
Arguments.of(new String[]{ "0001020304050607080000000000000000000000000000000000000000000000" }, 0, 1, 8, 6),
Arguments.of(new String[]{ "0001020304050607080000000000000000000000000000000000000000000000" }, 1, 0, 8, 6),
Arguments.of(new String[]{ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f", "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf", "c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf", "e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" }, 256, 256, 1, 9),
Arguments.of(new String[]{ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f", "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f", "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf", "c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf", "e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff" }, 0, 256, 256, 27)
);
}

}
96 changes: 96 additions & 0 deletions rskj-core/src/test/java/co/rsk/vm/opcode/MCopyInputTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package co.rsk.vm.opcode;

import co.rsk.config.TestSystemProperties;
import co.rsk.config.VmConfig;
import co.rsk.peg.BridgeSupportFactory;
import co.rsk.peg.RepositoryBtcBlockStoreWithCache;
import co.rsk.vm.BytecodeCompiler;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.core.*;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.PrecompiledContracts;
import org.ethereum.vm.VM;
import org.ethereum.vm.program.Program;
import org.ethereum.vm.program.invoke.ProgramInvokeMockImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.HashSet;
import java.util.stream.Stream;

import static co.rsk.net.utils.TransactionUtils.createTransaction;
import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP445;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class MCopyInputTest {

private ActivationConfig.ForBlock activationConfig;

private final ProgramInvokeMockImpl invoke = new ProgramInvokeMockImpl();
private final BytecodeCompiler compiler = new BytecodeCompiler();
private final TestSystemProperties config = new TestSystemProperties();
private final VmConfig vmConfig = config.getVmConfig();
private final SignatureCache signatureCache = new BlockTxSignatureCache(new ReceivedTxSignatureCache());
private final BlockFactory blockFactory = new BlockFactory(config.getActivationConfig());
private final Transaction transaction = createTransaction();
private final PrecompiledContracts precompiledContracts = new PrecompiledContracts(
config,
new BridgeSupportFactory(
new RepositoryBtcBlockStoreWithCache.Factory(
config.getNetworkConstants().getBridgeConstants().getBtcParams()),
config.getNetworkConstants().getBridgeConstants(),
config.getActivationConfig(), signatureCache), signatureCache);

@BeforeEach
void setup() {
activationConfig = mock(ActivationConfig.ForBlock.class);
when(activationConfig.isActive(RSKIP445)).thenReturn(true);
}

@ParameterizedTest
@MethodSource("provideParametersForOOGCases")
void testMCopy_ShouldThrowOOGException(String[] initMemory, int dst, int src, long length) {
// Given
byte[] code = compiler.compile("MCOPY");
VM vm = new VM(vmConfig, precompiledContracts);

Program program = new Program(vmConfig, precompiledContracts, blockFactory, activationConfig, code, invoke, transaction, new HashSet<>(), new BlockTxSignatureCache(new ReceivedTxSignatureCache()));

int address = 0;
for (String entry : initMemory) {
program.memorySave(DataWord.valueOf(address), DataWord.valueFromHex(entry));
address += 32;
}

program.stackPush(DataWord.valueOf(length)); // Mind the stack order!!
program.stackPush(DataWord.valueOf(src));
program.stackPush(DataWord.valueOf(dst));

// Then
Program.OutOfGasException ex = Assertions.assertThrows(Program.OutOfGasException.class, () -> executeProgram(vm, program));
Assertions.assertTrue(ex.getMessage().contains("Not enough gas for 'MCOPY' operation"));
}

private static Stream<Arguments> provideParametersForOOGCases() {
return Stream.of(
Arguments.of(new String[]{ "0000000000000000000000000000000000000000000000000000000000000000" }, 0, 0, -1),
Arguments.of(new String[]{}, 0, 0, -(2 * (Long.MAX_VALUE / 3))),
Arguments.of(new String[]{}, 0, 0, Integer.MAX_VALUE + 1L)
);
}

private static void executeProgram(VM vm, Program program) {
try {
while (!program.isStopped()) {
vm.step(program);
}
} catch(Program.StackTooSmallException e) {
Assertions.fail("Stack too small exception");
}
}

}

0 comments on commit 80838a5

Please sign in to comment.