diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json
index d766505..bb6ef56 100644
--- a/crates/integration/codesize.json
+++ b/crates/integration/codesize.json
@@ -2,9 +2,9 @@
"Baseline": 934,
"Computation": 4360,
"DivisionArithmetics": 39448,
- "ERC20": 52961,
+ "ERC20": 46624,
"Events": 1749,
"FibonacciIterative": 2973,
- "Flipper": 3549,
- "SHA1": 32786
+ "Flipper": 3563,
+ "SHA1": 32709
}
\ No newline at end of file
diff --git a/crates/integration/contracts/Storage.sol b/crates/integration/contracts/Storage.sol
new file mode 100644
index 0000000..b0c474b
--- /dev/null
+++ b/crates/integration/contracts/Storage.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+contract Storage {
+ function transient(uint value) public returns (uint ret) {
+ assembly {
+ let slot := 123
+ tstore(slot, value)
+ let success := call(0, 0, 0, 0, 0, 0, 0)
+ ret := tload(slot)
+ }
+ }
+}
diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs
index b636767..5d9ccec 100644
--- a/crates/integration/src/cases.rs
+++ b/crates/integration/src/cases.rs
@@ -160,6 +160,12 @@ sol!(
}
);
+sol!(
+ contract Storage {
+ function transient(uint value) public returns (uint ret);
+ }
+);
+
impl Contract {
/// Execute the contract.
///
@@ -526,6 +532,18 @@ impl Contract {
calldata: Bitwise::opByteCall::new((index, value)).abi_encode(),
}
}
+
+ pub fn storage_transient(value: U256) -> Self {
+ let code = include_str!("../contracts/Storage.sol");
+ let name = "Storage";
+
+ Self {
+ name,
+ evm_runtime: crate::compile_evm_bin_runtime(name, code),
+ pvm_runtime: crate::compile_blob(name, code),
+ calldata: Storage::transientCall::new((value,)).abi_encode(),
+ }
+ }
}
#[cfg(test)]
diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs
index 7aba163..89b81f2 100644
--- a/crates/integration/src/mock_runtime.rs
+++ b/crates/integration/src/mock_runtime.rs
@@ -106,6 +106,7 @@ impl Default for Frame {
#[derive(Default, Clone, Debug)]
pub struct Transaction {
state: State,
+ transient_state: State,
stack: Vec,
}
@@ -137,6 +138,16 @@ impl Transaction {
fn create2(&self, salt: B256, blob_hash: B256) -> Address {
self.top_frame().callee.create2(salt, blob_hash)
}
+
+ fn transient_account_mut(&mut self) -> &mut Account {
+ let address = self.top_frame().callee;
+ let account = self
+ .transient_state
+ .accounts_mut()
+ .entry(address)
+ .or_default();
+ account
+ }
}
/// Helper to create valid transactions.
@@ -277,6 +288,7 @@ impl From for TransactionBuilder {
state_before: state.clone(),
context: Transaction {
state,
+ transient_state: Default::default(),
stack: Default::default(),
},
}
@@ -312,6 +324,7 @@ impl State {
state_before: self.clone(),
context: Transaction {
state: self,
+ transient_state: Default::default(),
stack: vec![Default::default()],
},
}
@@ -435,6 +448,7 @@ fn link_host_functions(engine: &Engine) -> Linker {
.func_wrap(
runtime_api::imports::SET_STORAGE,
|caller: Caller,
+ transient: u32,
key_ptr: u32,
key_len: u32,
value_ptr: u32,
@@ -461,7 +475,12 @@ fn link_host_functions(engine: &Engine) -> Linker {
log::info!("set storage {key} = {value}");
- transaction.top_account_mut().storage.insert(key, value);
+ let storage = if transient == 0 {
+ &mut transaction.top_account_mut().storage
+ } else {
+ &mut transaction.transient_account_mut().storage
+ };
+ storage.insert(key, value);
Ok(0)
},
@@ -472,6 +491,7 @@ fn link_host_functions(engine: &Engine) -> Linker {
.func_wrap(
runtime_api::imports::GET_STORAGE,
|caller: Caller,
+ transient: u32,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
@@ -488,12 +508,13 @@ fn link_host_functions(engine: &Engine) -> Linker {
);
let key = U256::from_le_bytes::<32>(key.try_into().unwrap());
- let value = transaction
- .top_account_mut()
- .storage
- .get(&key)
- .cloned()
- .unwrap_or_default();
+
+ let storage = if transient == 0 {
+ &transaction.top_account_mut().storage
+ } else {
+ &transaction.transient_account_mut().storage
+ };
+ let value = storage.get(&key).cloned().unwrap_or_default();
log::info!("get storage {key} = {value}");
diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs
index 271c3cd..fa8d836 100644
--- a/crates/integration/src/tests.rs
+++ b/crates/integration/src/tests.rs
@@ -614,3 +614,16 @@ fn bitwise_byte() {
assert_eq!(expected, received)
}
}
+
+#[test]
+fn transient_storage() {
+ let expected = U256::MAX;
+ let (state, output) = assert_success(&Contract::storage_transient(expected), false);
+ let received = U256::abi_decode(&output.data, true).unwrap();
+ assert_eq!(expected, received);
+
+ assert!(state
+ .accounts()
+ .values()
+ .all(|account| account.storage.is_empty()));
+}
diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs
index 391ee7b..7fddf22 100644
--- a/crates/llvm-context/src/polkavm/context/mod.rs
+++ b/crates/llvm-context/src/polkavm/context/mod.rs
@@ -673,8 +673,7 @@ where
self.build_byte_swap(value)
}
- AddressSpace::TransientStorage => todo!(),
- AddressSpace::Storage => {
+ AddressSpace::Storage | AddressSpace::TransientStorage => {
let storage_key_value = self.builder().build_ptr_to_int(
pointer.value,
self.word_type(),
@@ -692,9 +691,12 @@ where
let (storage_value_pointer, storage_value_length_pointer) = self
.build_stack_parameter(revive_common::BIT_LENGTH_WORD, "storage_value_pointer");
+ let transient = pointer.address_space == AddressSpace::TransientStorage;
+
self.build_runtime_call(
runtime_api::imports::GET_STORAGE,
&[
+ self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer.to_int(self).into(),
@@ -758,8 +760,7 @@ where
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
}
- AddressSpace::TransientStorage => todo!(),
- AddressSpace::Storage => {
+ AddressSpace::Storage | AddressSpace::TransientStorage => {
assert_eq!(
value.as_basic_value_enum().get_type(),
self.word_type().as_basic_type_enum()
@@ -789,9 +790,12 @@ where
self.builder()
.build_store(storage_value_pointer.value, value)?;
+ let transient = pointer.address_space == AddressSpace::TransientStorage;
+
self.build_runtime_call(
runtime_api::imports::SET_STORAGE,
&[
+ self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer_casted.into(),
diff --git a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c
index f0c0f08..b9e5d58 100644
--- a/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c
+++ b/crates/pallet-contracts-pvm-llapi/src/polkavm_guest.c
@@ -67,9 +67,9 @@ POLKAVM_IMPORT(void, returndatacopy, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(void, value_transferred, uint32_t, uint32_t)
-POLKAVM_IMPORT(uint32_t, set_storage, uint32_t, uint32_t, uint32_t, uint32_t)
+POLKAVM_IMPORT(uint32_t, set_storage, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)
-POLKAVM_IMPORT(uint32_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t)
+POLKAVM_IMPORT(uint32_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, clear_storage, uint32_t, uint32_t)
diff --git a/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs b/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs
index 0af649c..b7626b9 100644
--- a/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs
+++ b/crates/solidity/src/evmla/ethereal_ir/function/block/element/mod.rs
@@ -742,12 +742,21 @@ where
.map(|_| None)
}
InstructionName::TLOAD => {
- let _arguments = self.pop_arguments_llvm(context);
- anyhow::bail!("The `TLOAD` instruction is not supported until zkVM v1.5.0");
+ let arguments = self.pop_arguments_llvm(context);
+ revive_llvm_context::polkavm_evm_storage::transient_load(
+ context,
+ arguments[0].into_int_value(),
+ )
+ .map(Some)
}
InstructionName::TSTORE => {
- let _arguments = self.pop_arguments_llvm(context);
- anyhow::bail!("The `TSTORE` instruction is not supported until zkVM v1.5.0");
+ let arguments = self.pop_arguments_llvm(context);
+ revive_llvm_context::polkavm_evm_storage::transient_store(
+ context,
+ arguments[0].into_int_value(),
+ arguments[1].into_int_value(),
+ )
+ .map(|_| None)
}
InstructionName::PUSHIMMUTABLE => {
let key = self
diff --git a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs
index 772e10d..ffdc3ee 100644
--- a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs
+++ b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs
@@ -485,18 +485,21 @@ impl FunctionCall {
.map(|_| None)
}
Name::TLoad => {
- let _arguments = self.pop_arguments_llvm::(context)?;
- anyhow::bail!(
- "{} The `TLOAD` instruction is not supported until zkVM v1.5.0",
- location
- );
+ let arguments = self.pop_arguments_llvm::(context)?;
+ revive_llvm_context::polkavm_evm_storage::transient_load(
+ context,
+ arguments[0].into_int_value(),
+ )
+ .map(Some)
}
Name::TStore => {
- let _arguments = self.pop_arguments_llvm::(context)?;
- anyhow::bail!(
- "{} The `TSTORE` instruction is not supported until zkVM v1.5.0",
- location
- );
+ let arguments = self.pop_arguments_llvm::(context)?;
+ revive_llvm_context::polkavm_evm_storage::transient_store(
+ context,
+ arguments[0].into_int_value(),
+ arguments[1].into_int_value(),
+ )
+ .map(|_| None)
}
Name::LoadImmutable => todo!(),
Name::SetImmutable => {