diff --git a/rskj-core/src/main/java/org/ethereum/core/Repository.java b/rskj-core/src/main/java/org/ethereum/core/Repository.java index 0cf1bfa183d..38b10af2f68 100644 --- a/rskj-core/src/main/java/org/ethereum/core/Repository.java +++ b/rskj-core/src/main/java/org/ethereum/core/Repository.java @@ -27,7 +27,7 @@ import java.math.BigInteger; -public interface Repository extends RepositorySnapshot { +public interface Repository extends RepositorySnapshot, TransientRepository { Trie getTrie(); /** diff --git a/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java b/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java new file mode 100644 index 00000000000..eee1f29d4ab --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/core/TransientRepository.java @@ -0,0 +1,40 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * 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 . + */ + +package org.ethereum.core; + +import co.rsk.core.RskAddress; +import org.ethereum.vm.DataWord; + +import javax.annotation.Nullable; + +public interface TransientRepository { + + void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value); + + void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value); + + void clearTransientStorage(); + + @Nullable + DataWord getTransientStorageValue(RskAddress addr, DataWord key); + + @Nullable + byte[] getTransientStorageBytes(RskAddress addr, DataWord key); +} \ No newline at end of file diff --git a/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java b/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java index 42dd7336b25..b3b00667002 100644 --- a/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java +++ b/rskj-core/src/main/java/org/ethereum/db/MutableRepository.java @@ -25,19 +25,29 @@ import co.rsk.crypto.Keccak256; import co.rsk.db.MutableTrieCache; import co.rsk.db.MutableTrieImpl; -import co.rsk.trie.*; +import co.rsk.trie.IterationElement; +import co.rsk.trie.MutableTrie; +import co.rsk.trie.Trie; +import co.rsk.trie.TrieKeySlice; +import co.rsk.trie.TrieStore; +import co.rsk.trie.TrieStoreImpl; import com.google.common.annotations.VisibleForTesting; import org.ethereum.core.AccountState; import org.ethereum.core.Repository; import org.ethereum.crypto.HashUtil; import org.ethereum.crypto.Keccak256Helper; +import org.ethereum.datasource.HashMapDB; import org.ethereum.vm.DataWord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.math.BigInteger; -import java.util.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; public class MutableRepository implements Repository { private static final Logger logger = LoggerFactory.getLogger("repository"); @@ -47,21 +57,32 @@ public class MutableRepository implements Repository { private final TrieKeyMapper trieKeyMapper; private final MutableTrie mutableTrie; + private MutableTrie transientTrie; private final IReadWrittenKeysTracker tracker; public MutableRepository(TrieStore trieStore, Trie trie) { - this(new MutableTrieImpl(trieStore, trie)); + this(new MutableTrieImpl(trieStore, trie), new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie())); } public MutableRepository(MutableTrie mutableTrie) { + this(mutableTrie, new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie())); + } + + public MutableRepository(MutableTrie mutableTrie, IReadWrittenKeysTracker tracker) { + this(mutableTrie, new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie()), tracker); + } + + public MutableRepository(MutableTrie mutableTrie, MutableTrie transientTrie) { this.trieKeyMapper = new TrieKeyMapper(); this.mutableTrie = mutableTrie; + this.transientTrie = transientTrie; this.tracker = new DummyReadWrittenKeysTracker(); } - public MutableRepository(MutableTrie mutableTrie, IReadWrittenKeysTracker tracker) { + public MutableRepository(MutableTrie mutableTrie, MutableTrie transientTrie, IReadWrittenKeysTracker tracker) { this.trieKeyMapper = new TrieKeyMapper(); this.mutableTrie = mutableTrie; + this.transientTrie = transientTrie; this.tracker = tracker; } @@ -327,7 +348,7 @@ public synchronized Set getAccountsKeys() { // To start tracking, a new repository is created, with a MutableTrieCache in the middle @Override public synchronized Repository startTracking() { - return new MutableRepository(new MutableTrieCache(mutableTrie), tracker); + return new MutableRepository(new MutableTrieCache(mutableTrie), new MutableTrieCache(transientTrie), tracker); } @Override @@ -338,11 +359,13 @@ public void save() { @Override public synchronized void commit() { mutableTrie.commit(); + transientTrie.commit(); } @Override public synchronized void rollback() { mutableTrie.rollback(); + transientTrie.rollback(); } @Override @@ -406,4 +429,48 @@ private Optional internalGetValueHash(byte[] key) { tracker.addNewReadKey(new ByteArrayWrapper(key)); return mutableTrie.getValueHash(key); } + + @Override + public void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value) { + addTransientStorageBytes(addr, key, value.getByteArrayForStorage()); + } + + @Override + public void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value) { + byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key); + + // Special case: if the value is an empty vector, we pass "null" which commands the trie to remove the item. + // Note that if the call comes from addStorageRow(), this method will already have replaced 0 by null, so the + // conversion here only applies if this is called directly. If suppose this only occurs in tests, but it can + // also occur in precompiled contracts that store data directly using this method. + if (value == null || value.length == 0) { + transientTrie.put(triekey, null); + } else { + transientTrie.put(triekey, value); + } + } + + @Override + public void clearTransientStorage() { + this.transientTrie = new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie()); + } + + @Nullable + @Override + public DataWord getTransientStorageValue(RskAddress addr, DataWord key) { + byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key); + byte[] value = transientTrie.get(triekey); + if (value == null) { + return null; + } + + return DataWord.valueOf(value); + } + + @Nullable + @Override + public byte[] getTransientStorageBytes(RskAddress addr, DataWord key) { + byte[] triekey = trieKeyMapper.getAccountStorageKey(addr, key); + return transientTrie.get(triekey); + } } diff --git a/rskj-core/src/main/java/org/ethereum/db/TransientStorageRepositoryCreator.java b/rskj-core/src/main/java/org/ethereum/db/TransientStorageRepositoryCreator.java new file mode 100644 index 00000000000..c03541fbe6f --- /dev/null +++ b/rskj-core/src/main/java/org/ethereum/db/TransientStorageRepositoryCreator.java @@ -0,0 +1,36 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * (derived from ethereumJ library, Copyright (c) 2016 ) + * + * 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 . + */ + +package org.ethereum.db; + +import co.rsk.db.MutableTrieImpl; +import co.rsk.trie.Trie; +import co.rsk.trie.TrieStoreImpl; +import org.ethereum.core.Repository; +import org.ethereum.datasource.HashMapDB; + +public class TransientStorageRepositoryCreator { + + private TransientStorageRepositoryCreator() { + } + + public static Repository createNewTransientStorage() { + return new MutableRepository(new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie())); + } +} diff --git a/rskj-core/src/main/java/org/ethereum/vm/VM.java b/rskj-core/src/main/java/org/ethereum/vm/VM.java index 6c5adc79246..7bed8d184ef 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/VM.java +++ b/rskj-core/src/main/java/org/ethereum/vm/VM.java @@ -1327,30 +1327,30 @@ else if (oldValue != null && newValue.isZero()) { } protected void doTLOAD(){ - DataWord key = program.stackPop(); - DataWord address = program.getOwnerAddress(); - if (isLogEnabled) { - logger.info("Executing TLOAD with parameters: address={} | key = {}", address, key); + logger.info("Executing TLOAD with parameters: key = {}", key); } + DataWord val = program.transientStorageLoad(key); - program.transientStorageSave(key, address); - // key could be returned to the pool, but storageLoad semantics should be checked + if (val == null) { + val = DataWord.ZERO; + } + + program.stackPush(val); + // key could be returned to the pool, but transientStorageLoad semantics should be checked // to make sure storageLoad always gets a copy, not a reference. program.step(); } protected void doTSTORE(){ - + DataWord address = program.stackPop(); DataWord value = program.stackPop(); - DataWord address = program.getOwnerAddress(); - DataWord key = DataWord.ZERO; if (isLogEnabled) { - logger.info("Executing TSTORE with parameters: address={} | key = {} | value = {}", address, key, value); + logger.info("Executing TSTORE with parameters: address={} | value = {}", address, value); } - program.transientStorageLoad(address, key, value); + program.transientStorageSave(address, value); program.step(); } @@ -1967,9 +1967,16 @@ protected void executeOpcode() { break; case OpCodes.OP_SSTORE: doSSTORE(); break; - case OpCodes.OP_TLOAD: doTLOAD(); + case OpCodes.OP_TLOAD: + if (!activations.isActive(RSKIP446)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } + doTLOAD(); break; case OpCodes.OP_TSTORE: doTSTORE(); + if (!activations.isActive(RSKIP446)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } break; case OpCodes.OP_JUMP: doJUMP(); break; diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index 2029b0c6f3b..87443ab6b84 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -72,8 +72,10 @@ import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -95,7 +97,6 @@ public class Program { private static final Logger logger = LoggerFactory.getLogger("VM"); private static final Logger gasLogger = LoggerFactory.getLogger("gas"); - public static final long MAX_MEMORY = (1<<30); //Max size for stack checks @@ -985,8 +986,8 @@ private void storageSave(byte[] key, byte[] val) { getStorage().addStorageRow(getOwnerRskAddress(), keyWord, valWord); } - public void transientStorageSave(DataWord key, DataWord address) { - + public void transientStorageSave(DataWord key, DataWord value) { + getStorage().addTransientStorageRow(getOwnerRskAddress(), key, value); } private RskAddress getOwnerRskAddress() { @@ -1099,8 +1100,8 @@ public DataWord storageLoad(DataWord key) { return getStorage().getStorageValue(getOwnerRskAddress(), key); } - public void transientStorageLoad(DataWord address, DataWord key, DataWord value) { - + public DataWord transientStorageLoad(DataWord key) { + return getStorage().getTransientStorageValue(getOwnerRskAddress(), key); } public DataWord getPrevHash() { diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java b/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java index 836e4d4122a..99802802b9b 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Storage.java @@ -30,6 +30,7 @@ import org.ethereum.vm.program.listener.ProgramListener; import org.ethereum.vm.program.listener.ProgramListenerAware; +import javax.annotation.Nullable; import java.math.BigInteger; import java.util.Iterator; import java.util.Set; @@ -223,4 +224,31 @@ public byte[] getRoot() { public void updateAccountState(RskAddress addr, AccountState accountState) { throw new UnsupportedOperationException(); } + + @Override + public void addTransientStorageRow(RskAddress addr, DataWord key, DataWord value) { + repository.addTransientStorageRow(addr, key, value); + } + + @Override + public void addTransientStorageBytes(RskAddress addr, DataWord key, byte[] value) { + repository.addTransientStorageBytes(addr, key, value); + } + + @Override + public void clearTransientStorage() { + repository.clearTransientStorage(); + } + + @Nullable + @Override + public DataWord getTransientStorageValue(RskAddress addr, DataWord key) { + return repository.getTransientStorageValue(addr, key); + } + + @Nullable + @Override + public byte[] getTransientStorageBytes(RskAddress addr, DataWord key) { + return repository.getTransientStorageBytes(addr, key); + } } diff --git a/rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageDslTest.java b/rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageDslTest.java index 1eec762bb0f..0a5b23b7467 100644 --- a/rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageDslTest.java +++ b/rskj-core/src/test/java/co/rsk/vm/opcode/TransientStorageDslTest.java @@ -28,6 +28,7 @@ import org.ethereum.core.Block; import org.ethereum.core.Transaction; import org.ethereum.core.TransactionReceipt; +import org.ethereum.core.util.TransactionReceiptUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -35,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; public class TransientStorageDslTest { @@ -45,23 +47,59 @@ void testTransientStorageOpcodesExecutionsWithRSKIPActivated() throws FileNotFou WorldDslProcessor processor = new WorldDslProcessor(world); processor.processCommands(parser); - String contractCreationTx = "txTestTransientStorageContract"; - Transaction contractTransaction = world.getTransactionByName("txTestTransientStorageContract"); - assertNotNull(contractTransaction); + String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract"; + assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true); - Block bestBlock = world.getBlockByName("b03"); - Assertions.assertEquals(1, bestBlock.getTransactionsList().size()); - TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName(contractCreationTx); + String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract"; + assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true); - assertNotNull(contractTransactionReceipt); - byte[] status = contractTransactionReceipt.getStatus(); - assertNotNull(status); - assertEquals(1, status.length); - assertEquals(1, status[0]); + String checkingOpcodesTxName = "txTestTransientStorageOpCodes"; + TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b03", true); + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null)); + + String checkingOpcodesTxName2 = "txTestTransientStorageOpCodesOtherValue"; + TransactionReceipt txReceipt2 = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName2, "b04", true); + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt2, "OK", null)); + } + + @Test + void testTransientStorageOpcodesShareMemorySameTransaction() throws FileNotFoundException, DslProcessorException { + DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt"); + World world = new World(); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract"; + assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true); + + String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract"; + assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true); + + String checkingOpcodesTxName = "txTestTransientStorageNestedTransactionShareMemory"; + TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b05", true); + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null)); + } + + @Test + void testTransientStorageOpcodesDoesntShareMemoryFromOtherContract() throws FileNotFoundException, DslProcessorException { + DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt"); + World world = new World(); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract"; + assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true); + + String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract"; + assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true); + + String checkingOpcodesTxName = "txTestTransientStorageNestedTransactionOtherContractDoesntShareMemory"; + TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b06", true); + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "ERROR", new String[]{"bytes32"})); } @Test - void testTransientStorageOpcodesExecutionsWithRSKIPDeactivated() throws FileNotFoundException, DslProcessorException { + void testTransientStorageOpcodesExecutionFailsWithRSKIPDeactivated() throws FileNotFoundException, DslProcessorException { TestSystemProperties rskip446Disabled = new TestSystemProperties(rawConfig -> rawConfig.withValue("blockchain.config.hardforkActivationHeights.lovell700", ConfigValueFactory.fromAnyRef(-1)) ); @@ -71,19 +109,36 @@ void testTransientStorageOpcodesExecutionsWithRSKIPDeactivated() throws FileNotF WorldDslProcessor processor = new WorldDslProcessor(world); processor.processCommands(parser); - String contractCreationTx = "txTestTransientStorageContract"; - Transaction contractTransaction = world.getTransactionByName("txTestTransientStorageContract"); - assertNotNull(contractTransaction); + String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract"; + assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true); + + String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract"; + assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true); + + String checkingOpcodesTxName = "txTestTransientStorageOpCodes"; + assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b03", false); + } - Block bestBlock = world.getBlockByName("b03"); - Assertions.assertEquals(1, bestBlock.getTransactionsList().size()); - TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName(contractCreationTx); + private static TransactionReceipt assertTransactionReceiptWithStatus(World world, String txName, String blockName, boolean withSuccess) { + Transaction txCreation = world.getTransactionByName(txName); + assertNotNull(txCreation); - assertNotNull(contractTransactionReceipt); - byte[] status = contractTransactionReceipt.getStatus(); + Block block = world.getBlockByName(blockName); + assertEquals(1, block.getTransactionsList().size()); + + TransactionReceipt txReceipt = world.getTransactionReceiptByName(txName); + assertNotNull(txReceipt); + + byte[] status = txReceipt.getStatus(); assertNotNull(status); - assertEquals(1, status.length); - assertEquals(1, status[0]); + + if(withSuccess) { + assertEquals(1, status.length); + assertEquals(1, status[0]); + } else { + assertEquals(0, status.length); + } + return txReceipt; } } diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java index 167d30f3f8b..760742b20cf 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java @@ -126,6 +126,7 @@ class ActivationConfigTest { " rskip434: arrowhead631", " rskip428: lovell700", " rskip438: lovell700", + " rskip446: lovell700", "}" )); diff --git a/rskj-core/src/test/resources/dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt b/rskj-core/src/test/resources/dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt index e8312fc8cb4..0607af0de7a 100644 --- a/rskj-core/src/test/resources/dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt +++ b/rskj-core/src/test/resources/dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt @@ -1,55 +1,108 @@ comment // CONTRACT CODE - pragma solidity ^0.8.24; contract TestTransientStorage { constructor() {} event OK(); - event ERROR(); + event ERROR(bytes32); - function checkTStore() external { - bytes32 valueToSave = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f; - address contractAddress = address(this); + function checkTransientStorage(bytes32 key, bytes32 value) external { + bytes32 valueLoaded; assembly { - tstore(contractAddress, valueToSave) // Use TSTORE to save the value at the contract address on the transient storage + tstore(key, value) + valueLoaded := tload(key) + } + if (valueLoaded == value) { + emit OK(); + } else { + emit ERROR(valueLoaded); } } - function checkTLoad() external { - bytes32 valueSaved = 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f; + function checkDataEqualsTheValuePassed(bytes32 key, bytes32 valueExpected) external { bytes32 valueLoaded; - address contractAddress = address(this); - assembly { - valueLoaded := tload(contractAddress) // Use TLOAD to load the value into the temporary variable + assembly { + valueLoaded := tload(key) } - if (valueLoaded == valueSaved) { + if (valueLoaded == valueExpected) { emit OK(); } else { - emit ERROR(); + emit ERROR(valueLoaded); + } + } + + function checkIfNestedTransactionSameContractShareMemory(bytes32 key, bytes32 value) external { + assembly { + tstore(key, value) + } + this.checkDataEqualsTheValuePassed(key, value); + } + + function checkIfNestedTransactionFromOtherContractShareMemory(bytes32 key, bytes32 value) external { + assembly { + tstore(key, value) + } + new TestTransientStorageOtherContract().checkDataEqualsTheValuePassed(key, value); + } +} + +contract TestTransientStorageOtherContract{ + constructor() {} + + event OK(); + event ERROR(bytes32); + + function checkDataEqualsTheValuePassed(bytes32 key, bytes32 valueExpected) external { + bytes32 valueLoaded; + assembly { + valueLoaded := tload(key) + } + if (valueLoaded == valueExpected) { + emit OK(); + } else { + emit ERROR(valueLoaded); } } } // DESCRIPTION -This contract contains two functions: checkTStore and checkTLoad. +TestTransientStorage has the following functions: -* checkTStore simply checks if the transaction finished with success and it's expected to have saved the value on the address +* checkTransientStorage simply checks if the TSTORE and TLOAD functions are working properly. It stores a value in the transient storage and then loads it. If the loaded value is the same as the stored value, it emits an OK event. Otherwise, it emits an ERROR event. -* checkTLoad simply checks if the transaction finished with success and check if the value loaded is the same as the value saved +* checkDataEqualsTheValuePassed simply checks if the transaction TLOAD doesn't load a value that was not stored in the transient storage. It loads a value from the transient storage, if we haven't loaded with a nested transaction it will return 0 otherwise it will return the value stored. +If the value loaded is the same as the value passed as a parameter, it emits an OK event. Otherwise, it emits an ERROR event. +* checkIfNestedTransactionSameContractShareMemory stores a value in the transient storage and then calls a function from the same contract that loads the value from the transient storage. This is to check if the transient storage is shared between nested transactions. + +* checkIfNestedTransactionFromOtherContractShareMemory stores a value in the transient storage and then calls a function from another contract that loads the value from the transient storage. This is to check if the transient storage is not shared between transactions. + +TestTransientStorageOtherContract has the following functions: + +* checkDataEqualsTheValuePassed simply checks if the transaction TLOAD doesn't load a value that was not stored in the transient storage. It loads a value from the transient storage, if we haven't loaded with a nested transaction it will return 0 otherwise it will return the value stored. +If the value loaded is the same as the value passed as a parameter, it emits an OK event. Otherwise, it emits an ERROR event. // CONTRACT BYTECODE -608060405234801561000f575f80fd5b5061014a8061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063a36ca99d14610038578063d5ad4e0b14610042575b5f80fd5b61004061004c565b005b61004a61007c565b005b5f7e0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f5f1b90505f30905081815d5050565b5f7e0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f5f1b90505f80309050805c91508282036100e2577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a161010f565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50505056fea2646970667358221220c97a07f12090a982975e295ffa092efe648f43916b098afb2523daffb7e42df464736f6c63430008180033 +TestTransientStorage: 6080604052348015600e575f80fd5b506105718061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80635dbdd0ce1461004e5780637b70ef4e1461006a5780637dccab6614610086578063f5cc67a1146100a2575b5f80fd5b610068600480360381019061006391906102fc565b6100be565b005b610084600480360381019061007f91906102fc565b61012d565b005b6100a0600480360381019061009b91906102fc565b6101c1565b005b6100bc60048036038101906100b791906102fc565b61023b565b005b80825d3073ffffffffffffffffffffffffffffffffffffffff16637dccab6683836040518363ffffffff1660e01b81526004016100fc929190610349565b5f604051808303815f87803b158015610113575f80fd5b505af1158015610125573d5f803e3d5ffd5b505050505050565b80825d60405161013c906102b8565b604051809103905ff080158015610155573d5f803e3d5ffd5b5073ffffffffffffffffffffffffffffffffffffffff16637dccab6683836040518363ffffffff1660e01b8152600401610190929190610349565b5f604051808303815f87803b1580156101a7575f80fd5b505af11580156101b9573d5f803e3d5ffd5b505050505050565b5f825c90508181036101fe577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a1610236565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd98160405161022d9190610370565b60405180910390a15b505050565b5f81835d825c905081810361027b577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16102b3565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516102aa9190610370565b60405180910390a15b505050565b6101b28061038a83390190565b5f80fd5b5f819050919050565b6102db816102c9565b81146102e5575f80fd5b50565b5f813590506102f6816102d2565b92915050565b5f8060408385031215610312576103116102c5565b5b5f61031f858286016102e8565b9250506020610330858286016102e8565b9150509250929050565b610343816102c9565b82525050565b5f60408201905061035c5f83018561033a565b610369602083018461033a565b9392505050565b5f6020820190506103835f83018461033a565b9291505056fe6080604052348015600e575f80fd5b506101968061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637dccab661461002d575b5f80fd5b610047600480360381019061004291906100fa565b610049565b005b5f825c9050818103610086577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100be565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516100b59190610147565b60405180910390a15b505050565b5f80fd5b5f819050919050565b6100d9816100c7565b81146100e3575f80fd5b50565b5f813590506100f4816100d0565b92915050565b5f80604083850312156101105761010f6100c3565b5b5f61011d858286016100e6565b925050602061012e858286016100e6565b9150509250929050565b610141816100c7565b82525050565b5f60208201905061015a5f830184610138565b9291505056fea2646970667358221220299e78f210bb50b25e55a7d3d761eb697663f501d6b877c5f17e651d5c7111fb64736f6c634300081a0033a2646970667358221220d99f2c2087ad3b0f2283262f95bb66f6becb8b8538af83c1a9a571716f0abb1b64736f6c634300081a0033 + +TestTransientStorageOtherContract: 6080604052348015600e575f80fd5b506101968061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637dccab661461002d575b5f80fd5b610047600480360381019061004291906100fa565b610049565b005b5f825c9050818103610086577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100be565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516100b59190610147565b60405180910390a15b505050565b5f80fd5b5f819050919050565b6100d9816100c7565b81146100e3575f80fd5b50565b5f813590506100f4816100d0565b92915050565b5f80604083850312156101105761010f6100c3565b5b5f61011d858286016100e6565b925050602061012e858286016100e6565b9150509250929050565b610141816100c7565b82525050565b5f60208201905061015a5f830184610138565b9291505056fea2646970667358221220299e78f210bb50b25e55a7d3d761eb697663f501d6b877c5f17e651d5c7111fb64736f6c634300081a0033 -// CONTRACT CALL +// CONTRACT CALLS -- checkTStore() -> a36ca99d -- checkTLoad() -> d5ad4e0b +TestTransientStorage: + + 7dccab66: checkDataEqualsTheValuePassed(bytes32,bytes32) + 7b70ef4e: checkIfNestedTransactionFromOtherContractShareMemory(bytes32,bytes32) + 5dbdd0ce: checkIfNestedTransactionSameContractShareMemory(bytes32,bytes32) + f5cc67a1: checkTransientStorage(bytes32,bytes32) + +TestTransientStorageOtherContract: +- 7dccab66 -> checkDataEqualsTheValuePassed(bytes32,bytes32) end @@ -61,8 +114,8 @@ transaction_build txTestTransientStorageContract sender acc1 receiverAddress 00 value 0 - data 608060405234801561000f575f80fd5b5061014a8061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063a36ca99d14610038578063d5ad4e0b14610042575b5f80fd5b61004061004c565b005b61004a61007c565b005b5f7e0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f5f1b90505f30905081815d5050565b5f7e0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f5f1b90505f80309050805c91508282036100e2577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a161010f565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50505056fea2646970667358221220c97a07f12090a982975e295ffa092efe648f43916b098afb2523daffb7e42df464736f6c63430008180033 - gas 1200000 + data 6080604052348015600e575f80fd5b506105718061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80635dbdd0ce1461004e5780637b70ef4e1461006a5780637dccab6614610086578063f5cc67a1146100a2575b5f80fd5b610068600480360381019061006391906102fc565b6100be565b005b610084600480360381019061007f91906102fc565b61012d565b005b6100a0600480360381019061009b91906102fc565b6101c1565b005b6100bc60048036038101906100b791906102fc565b61023b565b005b80825d3073ffffffffffffffffffffffffffffffffffffffff16637dccab6683836040518363ffffffff1660e01b81526004016100fc929190610349565b5f604051808303815f87803b158015610113575f80fd5b505af1158015610125573d5f803e3d5ffd5b505050505050565b80825d60405161013c906102b8565b604051809103905ff080158015610155573d5f803e3d5ffd5b5073ffffffffffffffffffffffffffffffffffffffff16637dccab6683836040518363ffffffff1660e01b8152600401610190929190610349565b5f604051808303815f87803b1580156101a7575f80fd5b505af11580156101b9573d5f803e3d5ffd5b505050505050565b5f825c90508181036101fe577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a1610236565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd98160405161022d9190610370565b60405180910390a15b505050565b5f81835d825c905081810361027b577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16102b3565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516102aa9190610370565b60405180910390a15b505050565b6101b28061038a83390190565b5f80fd5b5f819050919050565b6102db816102c9565b81146102e5575f80fd5b50565b5f813590506102f6816102d2565b92915050565b5f8060408385031215610312576103116102c5565b5b5f61031f858286016102e8565b9250506020610330858286016102e8565b9150509250929050565b610343816102c9565b82525050565b5f60408201905061035c5f83018561033a565b610369602083018461033a565b9392505050565b5f6020820190506103835f83018461033a565b9291505056fe6080604052348015600e575f80fd5b506101968061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637dccab661461002d575b5f80fd5b610047600480360381019061004291906100fa565b610049565b005b5f825c9050818103610086577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100be565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516100b59190610147565b60405180910390a15b505050565b5f80fd5b5f819050919050565b6100d9816100c7565b81146100e3575f80fd5b50565b5f813590506100f4816100d0565b92915050565b5f80604083850312156101105761010f6100c3565b5b5f61011d858286016100e6565b925050602061012e858286016100e6565b9150509250929050565b610141816100c7565b82525050565b5f60208201905061015a5f830184610138565b9291505056fea2646970667358221220299e78f210bb50b25e55a7d3d761eb697663f501d6b877c5f17e651d5c7111fb64736f6c634300081a0033a2646970667358221220d99f2c2087ad3b0f2283262f95bb66f6becb8b8538af83c1a9a571716f0abb1b64736f6c634300081a0033 + gas 1000000 build # Create block to hold txTestTransientStorageContract transaction @@ -77,21 +130,20 @@ block_connect b01 # Check b01 is best block assert_best b01 -# Create transaction to execute checkTStore() method -transaction_build txTestTStore +# Create transaction to deploy TestTransientStorageOtherContract contract +transaction_build txTestTransientStorageOtherContract sender acc1 nonce 1 - contract txTestTransientStorageContract + receiverAddress 00 value 0 - data a36ca99d - gas 30000 + data 6080604052348015600e575f80fd5b506101968061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637dccab661461002d575b5f80fd5b610047600480360381019061004291906100fa565b610049565b005b5f825c9050818103610086577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100be565b7f2fe0d3bd8bbb632441eefda46ec52f1ddda3a9e827d14c40f422037320f28bd9816040516100b59190610147565b60405180910390a15b505050565b5f80fd5b5f819050919050565b6100d9816100c7565b81146100e3575f80fd5b50565b5f813590506100f4816100d0565b92915050565b5f80604083850312156101105761010f6100c3565b5b5f61011d858286016100e6565b925050602061012e858286016100e6565b9150509250929050565b610141816100c7565b82525050565b5f60208201905061015a5f830184610138565b9291505056fea2646970667358221220299e78f210bb50b25e55a7d3d761eb697663f501d6b877c5f17e651d5c7111fb64736f6c634300081a0033 + gas 1000000 build -# Create block to hold txTestTStore transaction +# Create block to hold txTestTransientStorageOtherContract transaction block_build b02 parent b01 - transactions txTestTStore - gasLimit 6500000 + transactions txTestTransientStorageOtherContract build # Connect block @@ -100,25 +152,95 @@ block_connect b02 # Check b02 is best block assert_best b02 -# Create transaction to execute checkTLoad() method -transaction_build txTestTLoad +# Create transaction to execute checkTransientStorage(bytes32 key, bytes32 value) method +transaction_build txTestTransientStorageOpCodes sender acc1 nonce 2 contract txTestTransientStorageContract value 0 - data d5ad4e0b - gas 30000 + data f5cc67a131000000000000000000000000000000000000000000000000000000000000003200000000000000000000000000000000000000000000000000000000000000 + gas 200000 build -# Create block to hold txTestTLoad transaction +# Create block to hold txTestTransientStorageOpCodes transaction block_build b03 parent b02 - transactions txTestTLoad - gasLimit 6500000 + transactions txTestTransientStorageOpCodes + gasLimit 350000 build # Connect block block_connect b03 # Check b02 is best block -assert_best b03 \ No newline at end of file +assert_best b03 + +# Create transaction to execute checkTransientStorage(bytes32 key, bytes32 value) method +transaction_build txTestTransientStorageOpCodesOtherValue + sender acc1 + nonce 3 + contract txTestTransientStorageContract + value 0 + data f5cc67a132000000000000000000000000000000000000000000000000000000000000003132333435000000000000000000000000000000000000000000000000000000 + gas 200000 + build + +# Create block to hold txTestTransientStorageOpCodesOtherValue transaction +block_build b04 + parent b03 + transactions txTestTransientStorageOpCodesOtherValue + gasLimit 350000 + build + +# Connect block +block_connect b04 + +# Check b02 is best block +assert_best b04 + +# Create transaction to execute checkIfNestedTransactionSameContractShareMemory(bytes32 key, bytes32 value) method +transaction_build txTestTransientStorageNestedTransactionShareMemory + sender acc1 + nonce 4 + contract txTestTransientStorageContract + value 0 + data f5cc67a131300000000000000000000000000000000000000000000000000000000000003230000000000000000000000000000000000000000000000000000000000000 + gas 200000 + build + +# Create block to hold txTestTransientStorageNestedTransactionShareMemory transaction +block_build b05 + parent b04 + transactions txTestTransientStorageNestedTransactionShareMemory + gasLimit 350000 + build + +# Connect block +block_connect b05 + +# Check b02 is best block +assert_best b05 + +# Create transaction to execute checkIfNestedTransactionFromOtherContractShareMemory(bytes32 key, bytes32 value) method +transaction_build txTestTransientStorageNestedTransactionOtherContractDoesntShareMemory + sender acc1 + nonce 5 + contract txTestTransientStorageContract + value 0 + data 7b70ef4e31300000000000000000000000000000000000000000000000000000000000003230000000000000000000000000000000000000000000000000000000000000 + gas 200000 + build + +# Create block to hold txTestTransientStorageNestedTransactionOtherContractDoesntShareMemory transaction +block_build b06 + parent b05 + transactions txTestTransientStorageNestedTransactionOtherContractDoesntShareMemory + gasLimit 350000 + build + +# Connect block +block_connect b06 + +# Check b02 is best block +assert_best b06 +