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

fmacleal/integrate tstorage in transaction execution #2844

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
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";

//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