Skip to content

Commit

Permalink
fmacleal/integrate tstorage in transaction execution (#2844)
Browse files Browse the repository at this point in the history
* Added tests for dynamic execution contexts

- WIP
- Now we will add unit tests for underflow execution tests

* Added tests for dynamic execution contexts

-  We had to add unit tests for underflow execution tests since isn't possible write contract for these scenarios
- Now we have all the tests regarding execution context with different types of calls, missing the gas
cost calculation that will be done in a later task

* Addressing review comments
  • Loading branch information
fmacleal authored Nov 14, 2024
1 parent 99599fb commit ea7d304
Show file tree
Hide file tree
Showing 6 changed files with 523 additions and 0 deletions.
4 changes: 4 additions & 0 deletions rskj-core/src/main/java/org/ethereum/vm/VM.java
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,10 @@ 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 (program.isStaticCall()) {
throw Program.ExceptionHelper.modificationException(program);
}

DataWord key = program.stackPop();
DataWord value = program.stackPop();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,36 @@ void testDynamicExecutionContextWithStackOverflow() throws FileNotFoundException
Assertions.assertEquals(3, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

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

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

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

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

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

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

private static TransactionReceipt assertTransactionReceiptWithStatus(World world, String txName, String blockName, boolean withSuccess) {
Transaction txCreation = world.getTransactionByName(txName);
assertNotNull(txCreation);
Expand Down
151 changes: 151 additions & 0 deletions rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* This file is part of RskJ
* Copyright (C) 2024 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package co.rsk.vm.opcode;

import co.rsk.config.TestSystemProperties;
import co.rsk.config.VmConfig;
import co.rsk.vm.BytecodeCompiler;
import org.ethereum.config.blockchain.upgrades.ActivationConfig;
import org.ethereum.core.BlockFactory;
import org.ethereum.core.BlockTxSignatureCache;
import org.ethereum.core.ReceivedTxSignatureCache;
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.Stack;
import org.ethereum.vm.program.invoke.ProgramInvokeMockImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashSet;

import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP446;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class TransientStorageTest {
private final TestSystemProperties config = new TestSystemProperties();
private final BlockFactory blockFactory = new BlockFactory(config.getActivationConfig());
private final PrecompiledContracts precompiledContracts = new PrecompiledContracts(config, null, new BlockTxSignatureCache(new ReceivedTxSignatureCache()));
private VmConfig vmConfig = config.getVmConfig();
private ProgramInvokeMockImpl invoke;
private BytecodeCompiler compiler;

@BeforeEach
void setup() {
invoke = new ProgramInvokeMockImpl();
compiler = new BytecodeCompiler();
}

@Test
void testTLoadDynamicExecutionContextUnderflow(){
//given
ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(RSKIP446)).thenReturn(true);

//when-then
Assertions.assertThrows(Program.StackTooSmallException.class, () -> executeCodeWithActivationConfig("TLOAD", 2, activations));
}

@Test
void testTLoadDynamicExecutionContextWorksFine(){
//given
ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(RSKIP446)).thenReturn(true);
String expected = "0000000000000000000000000000000000000000000000000000000000000000";

//when
Program program = executeCodeWithActivationConfig("PUSH32 0x0000000000000000000000000000000000000000000000000000000000000001 TLOAD", 2, activations);
Stack stack = program.getStack();

//then
assertEquals(1, stack.size());
assertEquals(DataWord.valueFromHex(expected), stack.peek());
}


@Test
void testTStoreDynamicExecutionContextUnderflow(){
//given
ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(RSKIP446)).thenReturn(true);

//when-then
Assertions.assertThrows(Program.StackTooSmallException.class, () -> executeCodeWithActivationConfig("TSTORE", 3, activations));
}


@Test
void testTStoreDynamicExecutionContextWorksFine(){
//given
ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(RSKIP446)).thenReturn(true);
String expected = "0000000000000000000000000000000000000000000000000000000000000000";

Check notice

Code scanning / CodeQL

Unread local variable Note test

Variable 'String expected' is never read.

//when
Program program = executeCodeWithActivationConfig("PUSH32 0x0000000000000000000000000000000000000000000000000000000000000420 " +
"PUSH32 0x0000000000000000000000000000000000000000000000000000000000000001 " +
"TSTORE", 3, activations);
Stack stack = program.getStack();

//then
assertEquals(0, stack.size());
assertNull(program.getResult().getException());
}

@Test
void testTStoreTloadDynamicExecutionContextWorksFine(){
//given
ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class);
when(activations.isActive(RSKIP446)).thenReturn(true);
String expected = "0000000000000000000000000000000000000000000000000000000000000420";

//when
Program program = executeCodeWithActivationConfig("PUSH32 0x0000000000000000000000000000000000000000000000000000000000000420 " +
"PUSH32 0x0000000000000000000000000000000000000000000000000000000000000001 " +
"TSTORE " +
"PUSH32 0x0000000000000000000000000000000000000000000000000000000000000001 " +
"TLOAD ", 5, activations);
Stack stack = program.getStack();

//then
assertEquals(1, stack.size());
assertEquals(DataWord.valueFromHex(expected), stack.peek());
}


private Program executeCodeWithActivationConfig(String code, int nsteps, ActivationConfig.ForBlock activations) {
return executeCodeWithActivationConfig(compiler.compile(code), nsteps, activations);
}
private Program executeCodeWithActivationConfig(byte[] code, int nsteps, ActivationConfig.ForBlock activations) {
VM vm = new VM(vmConfig, precompiledContracts);
Program program = new Program(vmConfig, precompiledContracts, blockFactory, activations, code, invoke,null, new HashSet<>(), new BlockTxSignatureCache(new ReceivedTxSignatureCache()));

for (int k = 0; k < nsteps; k++) {
vm.step(program);
}

return program;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
comment

// CONTRACT CODE
pragma solidity ^0.8.24;

contract TestTransientStorageCallContext {

constructor() {
}

event OK();
event ERROR(string, uint256);

function testCall() external {
// Deploy the Callee contract
address calleeAddress = address(new Callee());
uint256 success;
uint256 valueLoadedFrom0;
uint256 valueLoadedFrom1;
uint256 valueLoadedSstore0;
uint256 valueLoadedSstore1;
bytes4 executeSignature = bytes4(keccak256("execute()"));

assembly {
tstore(0, 420)
let availablePointer := mload(0x40)
mstore(availablePointer, executeSignature)
success := call(gas(), calleeAddress, 0, availablePointer, 0x4, availablePointer, 0x20)
valueLoadedFrom0 := tload(0)
valueLoadedFrom1 := tload(1)
valueLoadedSstore0 := sload(0)
valueLoadedSstore1 := sload(1)
}

checkReturnValueExpected(success, 'Checking result callee execution', 1);
checkReturnValueExpected(valueLoadedFrom0, 'Checking value from tload 0', 420);
checkReturnValueExpected(valueLoadedFrom1, 'Checking value from tload 1', 0);

checkReturnValueExpected(valueLoadedSstore0, 'Checking value from sstore 0', 0);
checkReturnValueExpected(valueLoadedSstore1, 'Checking value from sstore 1', 0);
}

function checkReturnValueExpected(uint256 valueReceived, string memory message, uint256 expectedValue) private {
if( valueReceived == expectedValue){
emit OK();
} else {
emit ERROR(message, valueReceived);
}
}
}

contract Callee {

event OK();
event ERROR(string, uint256);

function execute() external {
uint256 valueLoadedFrom1;
assembly {
sstore(0, tload(0))
tstore(1, 69)
sstore(1, tload(1))
valueLoadedFrom1 := tload(1)
}
if( valueLoadedFrom1 == 69){
emit OK();
} else {
emit ERROR('Checking value from tload 1 in callee', valueLoadedFrom1);
}
}
}

// CONTRACT BYTECODE

TestTransientStorageCallContext: 6080604052348015600e575f5ffd5b506105458061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063b7f058361461002d575b5f5ffd5b610035610037565b005b5f60405161004490610276565b604051809103905ff08015801561005d573d5f5f3e3d5ffd5b5090505f5f5f5f5f5f7f614619540b5b5abe478b88f28a37eb328054be3b41a7570ad5e8b701113364c490506101a45f5d6040518181526020816004835f8c5af196505f5c955060015c94505f5493506001549250506100f4866040518060400160405280602081526020017f436865636b696e6720726573756c742063616c6c656520657865637574696f6e81525060016101ff565b610136856040518060400160405280601b81526020017f436865636b696e672076616c75652066726f6d20746c6f6164203000000000008152506101a46101ff565b610176846040518060400160405280601b81526020017f436865636b696e672076616c75652066726f6d20746c6f6164203100000000008152505f6101ff565b6101b6836040518060400160405280601c81526020017f436865636b696e672076616c75652066726f6d207373746f72652030000000008152505f6101ff565b6101f6826040518060400160405280601c81526020017f436865636b696e672076616c75652066726f6d207373746f72652031000000008152505f6101ff565b50505050505050565b808303610237577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a1610271565b7fc9e730d5b570f89e168eb8c3d29f8c396b957e540af248c95c9519ac47c2c69f828460405161026892919061030b565b60405180910390a15b505050565b6101d68061033a83390190565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102c582610283565b6102cf818561028d565b93506102df81856020860161029d565b6102e8816102ab565b840191505092915050565b5f819050919050565b610305816102f3565b82525050565b5f6040820190508181035f83015261032381856102bb565b905061033260208301846102fc565b939250505056fe6080604052348015600e575f5ffd5b506101ba8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063614619541461002d575b5f5ffd5b610035610037565b005b5f5f5c5f55604560015d60015c60015560015c905060458103610085577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100bd565b7fc9e730d5b570f89e168eb8c3d29f8c396b957e540af248c95c9519ac47c2c69f816040516100b49190610158565b60405180910390a15b50565b5f82825260208201905092915050565b7f436865636b696e672076616c75652066726f6d20746c6f6164203120696e20635f8201527f616c6c6565000000000000000000000000000000000000000000000000000000602082015250565b5f61012a6025836100c0565b9150610135826100d0565b604082019050919050565b5f819050919050565b61015281610140565b82525050565b5f6040820190508181035f83015261016f8161011e565b905061017e6020830184610149565b9291505056fea2646970667358221220525015013274fe464b5371de8d79b5a498b2073f16a0ead556b61bb8daa945d664736f6c634300081c0033a2646970667358221220fe0d0f67ed34e641f6305bc910b9eb817769785a12a0f1837fe2371e930c5d1a64736f6c634300081c0033

b7f05836: testCall()

end

# Create and fund new account
account_new acc1 10000000

# Create transaction to deploy TestTransientStorageCallContext contract
transaction_build txContextCallSubcallContract
sender acc1
receiverAddress 00
value 0
data 6080604052348015600e575f5ffd5b506105458061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063b7f058361461002d575b5f5ffd5b610035610037565b005b5f60405161004490610276565b604051809103905ff08015801561005d573d5f5f3e3d5ffd5b5090505f5f5f5f5f5f7f614619540b5b5abe478b88f28a37eb328054be3b41a7570ad5e8b701113364c490506101a45f5d6040518181526020816004835f8c5af196505f5c955060015c94505f5493506001549250506100f4866040518060400160405280602081526020017f436865636b696e6720726573756c742063616c6c656520657865637574696f6e81525060016101ff565b610136856040518060400160405280601b81526020017f436865636b696e672076616c75652066726f6d20746c6f6164203000000000008152506101a46101ff565b610176846040518060400160405280601b81526020017f436865636b696e672076616c75652066726f6d20746c6f6164203100000000008152505f6101ff565b6101b6836040518060400160405280601c81526020017f436865636b696e672076616c75652066726f6d207373746f72652030000000008152505f6101ff565b6101f6826040518060400160405280601c81526020017f436865636b696e672076616c75652066726f6d207373746f72652031000000008152505f6101ff565b50505050505050565b808303610237577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a1610271565b7fc9e730d5b570f89e168eb8c3d29f8c396b957e540af248c95c9519ac47c2c69f828460405161026892919061030b565b60405180910390a15b505050565b6101d68061033a83390190565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6102c582610283565b6102cf818561028d565b93506102df81856020860161029d565b6102e8816102ab565b840191505092915050565b5f819050919050565b610305816102f3565b82525050565b5f6040820190508181035f83015261032381856102bb565b905061033260208301846102fc565b939250505056fe6080604052348015600e575f5ffd5b506101ba8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063614619541461002d575b5f5ffd5b610035610037565b005b5f5f5c5f55604560015d60015c60015560015c905060458103610085577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100bd565b7fc9e730d5b570f89e168eb8c3d29f8c396b957e540af248c95c9519ac47c2c69f816040516100b49190610158565b60405180910390a15b50565b5f82825260208201905092915050565b7f436865636b696e672076616c75652066726f6d20746c6f6164203120696e20635f8201527f616c6c6565000000000000000000000000000000000000000000000000000000602082015250565b5f61012a6025836100c0565b9150610135826100d0565b604082019050919050565b5f819050919050565b61015281610140565b82525050565b5f6040820190508181035f83015261016f8161011e565b905061017e6020830184610149565b9291505056fea2646970667358221220525015013274fe464b5371de8d79b5a498b2073f16a0ead556b61bb8daa945d664736f6c634300081c0033a2646970667358221220fe0d0f67ed34e641f6305bc910b9eb817769785a12a0f1837fe2371e930c5d1a64736f6c634300081c0033
gas 1000000
build

# Create block to hold txContextCallSubcallContract transaction
block_build b01
parent g00
transactions txContextCallSubcallContract
gasLimit 1200000
build

# Connect block
block_connect b01

# Check b01 is best block
assert_best b01

# Create transaction to execute txExecuteCallCode transaction
transaction_build txExecuteCallCode
sender acc1
nonce 1
contract txContextCallSubcallContract
value 0
data b7f05836
gas 1000000
build

# Create block to hold txExecuteCallCode transaction
block_build b02
parent b01
transactions txExecuteCallCode
gasLimit 2000000
build

# Connect block
block_connect b02

# Check b02 is best block
assert_best b02
Loading

0 comments on commit ea7d304

Please sign in to comment.