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

Add gas cost #2862

Merged
merged 12 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
nagarev marked this conversation as resolved.
Show resolved Hide resolved

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;

public class MCopyGasTest {
nagarev marked this conversation as resolved.
Show resolved Hide resolved

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;

public 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");
}
}

}
Loading