Skip to content

Commit

Permalink
Merge pull request #2870 from rsksmart/fmacleal/tstorage-gas-cost-add…
Browse files Browse the repository at this point in the history
…ition

Transient Storage gas cost calculation
  • Loading branch information
Vovchyk authored Dec 10, 2024
2 parents ac450fc + d86e88b commit f766549
Show file tree
Hide file tree
Showing 14 changed files with 1,636 additions and 12 deletions.
3 changes: 3 additions & 0 deletions rskj-core/src/main/java/org/ethereum/vm/GasCost.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public class GasCost {
public static final long REFUND_SSTORE = 15000;
public static final long CREATE = 32000;

public static final long TLOAD = 100;
public static final long TSTORE = 100;

public static final long JUMPDEST = 1;
public static final long CREATE_DATA_BYTE = 5;
public static final long CALL = 700;
Expand Down
28 changes: 17 additions & 11 deletions rskj-core/src/main/java/org/ethereum/vm/VM.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,37 +63,37 @@
/**
* The Ethereum Virtual Machine (EVM) is responsible for initialization
* and executing a transaction on a contract.
*
* It is a quasi-Turing-complete machine; the quasi qualification
* comes from the fact that the computation is intrinsically bounded
* through a parameter, gas, which limits the total amount of computation done.
*
* The EVM is a simple stack-based architecture. The word size of the machine
* (and thus size of stack item) is 256-bit. This was chosen to facilitate
* the SHA3-256 hash scheme and elliptic-curve computations. The memory model
* is a simple word-addressed byte array. The stack has an unlimited size.
* The machine also has an independent storage model; this is similar in concept
* to the memory but rather than a byte array, it is a word-addressable word array.
*
* Unlike memory, which is volatile, storage is non volatile and is
* maintained as part of the system state. All locations in both storage
* and memory are well-defined initially as zero.
*
* The machine does not follow the standard von Neumann architecture.
* Rather than storing program code in generally-accessible memory or storage,
* it is stored separately in a virtual ROM interactable only though
* a specialised instruction.
*
* The machine can have exceptional execution for several reasons,
* including stack underflows and invalid instructions. These unambiguously
* and validly result in immediate halting of the machine with all state changes
* left intact. The one piece of exceptional execution that does not leave
* state changes intact is the out-of-gas (OOG) exception.
*
* Here, the machine halts immediately and reports the issue to
* the execution agent (either the transaction processor or, recursively,
* the spawning execution environment) and which will deal with it separately.
*
* @author Roman Mandeleil
* @since 01.06.2014
*/
Expand Down Expand Up @@ -1340,8 +1340,11 @@ else if (oldValue != null && newValue.isZero()) {
}

protected void doTLOAD(){
//TODO: Gas cost calculation will be done here and also shared contexts verifications for
// different types of calls
if (computeGas) {
gasCost = GasCost.TLOAD;
spendOpCodeGas();
}

DataWord key = program.stackPop();
if (isLogEnabled) {
logger.info("Executing TLOAD with parameters: key = {}", key);
Expand All @@ -1359,8 +1362,11 @@ protected void doTLOAD(){
}

protected void doTSTORE(){
//TODO: Gas cost calculation will be done here and also shared contexts verifications for
// different types of calls
if (computeGas) {
gasCost = GasCost.TSTORE;
spendOpCodeGas();
}

if (program.isStaticCall()) {
throw Program.ExceptionHelper.modificationException(program);
}
Expand Down
198 changes: 197 additions & 1 deletion rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageDslTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.math.BigInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class TransientStorageDslTest {
Expand Down Expand Up @@ -341,6 +341,202 @@ void testDynamicExecutionStaticCallSubcallCantUseTstore() throws FileNotFoundExc
Assertions.assertEquals(2, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testDynamicReentrancyContextsTstoreBeforeRevertOrInvalidHasNoEffect() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/dynamic_reentrancy_context_tstore_before_revert_or_invalid_has_no_effect.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageDynamicReentrancyContextContract = "txTstorageDynamicReentrancyContextContract";
assertTransactionReceiptWithStatus(world, txTstorageDynamicReentrancyContextContract, "b01", true);

String txTestReentrantContextRevert = "txTestReentrantContextRevert";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txTestReentrantContextRevert, "b02", true);
Assertions.assertEquals(3, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));

String txTestReentrantContextInvalid = "txTestReentrantContextInvalid";
assertTransactionReceiptWithStatus(world, txTestReentrantContextInvalid, "b03", false);
}

@Test
void testDynamicReentrancyContextsRevertOrInvalidUndoesAll() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/dynamic_reentrancy_context_revert_or_invalid_undoes_all.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageDynamicReentrancyContextContract = "txTstorageDynamicReentrancyContextContract";
assertTransactionReceiptWithStatus(world, txTstorageDynamicReentrancyContextContract, "b01", true);

String txTestReentrantContextRevert = "txTestReentrantContextRevert";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txTestReentrantContextRevert, "b02", true);
Assertions.assertEquals(5, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));

String txTestReentrantContextInvalid = "txTestReentrantContextInvalid";
assertTransactionReceiptWithStatus(world, txTestReentrantContextInvalid, "b03", false);
}

@Test
void testDynamicReentrancyContextsRevertOrInvalidUndoesTstorageAfterSuccessfullCall() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/dynamic_reentrancy_context_revert_or_invalid_undoes_tstorage_after_successfull_call.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageDynamicReentrancyContextContract = "txTstorageDynamicReentrancyContextContract";
assertTransactionReceiptWithStatus(world, txTstorageDynamicReentrancyContextContract, "b01", true);

String txTstoreInDoubleReentrantCallWithRevert = "txTstoreInDoubleReentrantCallWithRevert";
assertTransactionReceiptWithStatus(world, txTstoreInDoubleReentrantCallWithRevert, "b02", true);

String txCheckValuesStoredInTstorageForRevert = "txCheckValuesStoredInTstorageForRevert";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorageForRevert, "b03", true);
Assertions.assertEquals(4, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));

String txTstoreInDoubleReentrantCallWithInvalid = "txTstoreInDoubleReentrantCallWithInvalid";
assertTransactionReceiptWithStatus(world, txTstoreInDoubleReentrantCallWithInvalid, "b04", false);

String txCheckValuesStoredInTstorageForInvalid = "txCheckValuesStoredInTstorageForInvalid";
txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorageForInvalid, "b05", true);
Assertions.assertEquals(4, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testReentrancyContextsTstoreAfterReentrantCall() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/reentrancy_context_tstore_after_reentrant_call.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageReentrancyContextTestContract = "txTstorageReentrancyContextTestContract";
assertTransactionReceiptWithStatus(world, txTstorageReentrancyContextTestContract, "b01", true);

String txTstoreInReentrantCall = "txTstoreInReentrantCall";
assertTransactionReceiptWithStatus(world, txTstoreInReentrantCall, "b02", true);

String txCheckValuesStoredInTstorage = "txCheckValuesStoredInTstorage";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorage, "b03", true);
Assertions.assertEquals(3, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testReentrancyContextsTloadAfterReentrantTstore() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/reentrancy_context_tload_after_reentrant_tstore.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageReentrancyContextTestContract = "txTstorageReentrancyContextTestContract";
assertTransactionReceiptWithStatus(world, txTstorageReentrancyContextTestContract, "b01", true);

String txTloadAfterReentrantTstore = "txTloadAfterReentrantTstore";
assertTransactionReceiptWithStatus(world, txTloadAfterReentrantTstore, "b02", true);

String txCheckValuesStoredInTstorage = "txCheckValuesStoredInTstorage";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorage, "b03", true);
Assertions.assertEquals(3, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testReentrancyContextsManipulateInReentrantCall() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/reentrancy_context_manipulate_in_reentrant_call.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageReentrancyContextTestContract = "txTstorageReentrancyContextTestContract";
assertTransactionReceiptWithStatus(world, txTstorageReentrancyContextTestContract, "b01", true);

String txManipulateInReentrantCall = "txManipulateInReentrantCall";
assertTransactionReceiptWithStatus(world, txManipulateInReentrantCall, "b02", true);

String txCheckValuesStoredInTstorage = "txCheckValuesStoredInTstorage";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorage, "b03", true);
Assertions.assertEquals(4, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testReentrancyContextsTstoreInCallThenTloadReturnInStaticCall() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/reentrancy_context_tstore_in_call_then_tload_return_in_static_call.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageReentrancyContextTestContract = "txTstorageReentrancyContextTestContract";
assertTransactionReceiptWithStatus(world, txTstorageReentrancyContextTestContract, "b01", true);

String txTstorageInReentrantCallTest = "txTstorageInReentrantCallTest";
assertTransactionReceiptWithStatus(world, txTstorageInReentrantCallTest, "b02", true);

String txCheckValuesStoredInTstorage = "txCheckValuesStoredInTstorage";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckValuesStoredInTstorage, "b03", true);
Assertions.assertEquals(5, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testTransientStorageGasMeasureTests() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tstorage_gas_measure_tests.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstorageGasMeasureTestContract = "txTstorageGasMeasureTestContract";
assertTransactionReceiptWithStatus(world, txTstorageGasMeasureTestContract, "b01", true);

String txCheckGasMeasures = "txCheckGasMeasures";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txCheckGasMeasures, "b02", true);
Assertions.assertEquals(4, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testTstoreLoopUntilOutOfGas() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tstore_loop_until_out_of_gas.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstoreLoopUntilOutOfGasContract = "txTstoreLoopUntilOutOfGasContract";
assertTransactionReceiptWithStatus(world, txTstoreLoopUntilOutOfGasContract, "b01", true);

String txRunTstoreUntilOutOfGas = "txRunTstoreUntilOutOfGas";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txRunTstoreUntilOutOfGas, "b02", false);
long txRunOutOfGas = new BigInteger(1, txReceipt.getGasUsed()).longValue();
assertEquals(300000, txRunOutOfGas); // Assert that it consumed all the gas configured in the transaction
}

@Test
void testTstoreWideAddressSpaceLoopUntilOutOfGas() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tstore_wide_address_space_loop_until_out_of_gas.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstoreWideAddressSpaceLoopUntilOutOfGasContract = "txTstoreWideAddressSpaceLoopUntilOutOfGasContract";
assertTransactionReceiptWithStatus(world, txTstoreWideAddressSpaceLoopUntilOutOfGasContract, "b01", true);

String txRunTstoreWideAddressSpaceUntilOutOfGas = "txRunTstoreWideAddressSpaceUntilOutOfGas";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txRunTstoreWideAddressSpaceUntilOutOfGas, "b02", false);
long txRunOutOfGas = new BigInteger(1, txReceipt.getGasUsed()).longValue();
assertEquals(500000, txRunOutOfGas); // Assert that it consumed all the gas configured in the transaction
}

@Test
void testTstoreAndTloadLoopUntilOutOfGas() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tstore_and_tload_loop_until_out_of_gas.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String txTstoreAndTloadLoopUntilOutOfGasContract = "txTstoreAndTloadLoopUntilOutOfGasContract";
assertTransactionReceiptWithStatus(world, txTstoreAndTloadLoopUntilOutOfGasContract, "b01", true);

String txRunTstoreAndTloadUntilOutOfGas = "txRunTstoreAndTloadUntilOutOfGas";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, txRunTstoreAndTloadUntilOutOfGas, "b02", false);
long txRunOutOfGas = new BigInteger(1, txReceipt.getGasUsed()).longValue();
assertEquals(700000, txRunOutOfGas); // Assert that it consumed all the gas configured in the transaction
}

private static TransactionReceipt assertTransactionReceiptWithStatus(World world, String txName, String blockName, boolean withSuccess) {
Transaction txCreation = world.getTransactionByName(txName);
assertNotNull(txCreation);
Expand Down
Loading

0 comments on commit f766549

Please sign in to comment.