implement transient storage

Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2024-06-05 17:34:17 +02:00
parent 9e9227d740
commit 39d78179d4
9 changed files with 112 additions and 30 deletions
+3 -3
View File
@@ -2,9 +2,9 @@
"Baseline": 934, "Baseline": 934,
"Computation": 4360, "Computation": 4360,
"DivisionArithmetics": 39448, "DivisionArithmetics": 39448,
"ERC20": 52961, "ERC20": 46624,
"Events": 1749, "Events": 1749,
"FibonacciIterative": 2973, "FibonacciIterative": 2973,
"Flipper": 3549, "Flipper": 3563,
"SHA1": 32786 "SHA1": 32709
} }
+14
View File
@@ -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)
}
}
}
+18
View File
@@ -160,6 +160,12 @@ sol!(
} }
); );
sol!(
contract Storage {
function transient(uint value) public returns (uint ret);
}
);
impl Contract { impl Contract {
/// Execute the contract. /// Execute the contract.
/// ///
@@ -526,6 +532,18 @@ impl Contract {
calldata: Bitwise::opByteCall::new((index, value)).abi_encode(), 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)] #[cfg(test)]
+28 -7
View File
@@ -106,6 +106,7 @@ impl Default for Frame {
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct Transaction { pub struct Transaction {
state: State, state: State,
transient_state: State,
stack: Vec<Frame>, stack: Vec<Frame>,
} }
@@ -137,6 +138,16 @@ impl Transaction {
fn create2(&self, salt: B256, blob_hash: B256) -> Address { fn create2(&self, salt: B256, blob_hash: B256) -> Address {
self.top_frame().callee.create2(salt, blob_hash) 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. /// Helper to create valid transactions.
@@ -277,6 +288,7 @@ impl From<State> for TransactionBuilder {
state_before: state.clone(), state_before: state.clone(),
context: Transaction { context: Transaction {
state, state,
transient_state: Default::default(),
stack: Default::default(), stack: Default::default(),
}, },
} }
@@ -312,6 +324,7 @@ impl State {
state_before: self.clone(), state_before: self.clone(),
context: Transaction { context: Transaction {
state: self, state: self,
transient_state: Default::default(),
stack: vec![Default::default()], stack: vec![Default::default()],
}, },
} }
@@ -435,6 +448,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
.func_wrap( .func_wrap(
runtime_api::imports::SET_STORAGE, runtime_api::imports::SET_STORAGE,
|caller: Caller<Transaction>, |caller: Caller<Transaction>,
transient: u32,
key_ptr: u32, key_ptr: u32,
key_len: u32, key_len: u32,
value_ptr: u32, value_ptr: u32,
@@ -461,7 +475,12 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
log::info!("set storage {key} = {value}"); 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) Ok(0)
}, },
@@ -472,6 +491,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
.func_wrap( .func_wrap(
runtime_api::imports::GET_STORAGE, runtime_api::imports::GET_STORAGE,
|caller: Caller<Transaction>, |caller: Caller<Transaction>,
transient: u32,
key_ptr: u32, key_ptr: u32,
key_len: u32, key_len: u32,
out_ptr: u32, out_ptr: u32,
@@ -488,12 +508,13 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
); );
let key = U256::from_le_bytes::<32>(key.try_into().unwrap()); let key = U256::from_le_bytes::<32>(key.try_into().unwrap());
let value = transaction
.top_account_mut() let storage = if transient == 0 {
.storage &transaction.top_account_mut().storage
.get(&key) } else {
.cloned() &transaction.transient_account_mut().storage
.unwrap_or_default(); };
let value = storage.get(&key).cloned().unwrap_or_default();
log::info!("get storage {key} = {value}"); log::info!("get storage {key} = {value}");
+13
View File
@@ -614,3 +614,16 @@ fn bitwise_byte() {
assert_eq!(expected, received) 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()));
}
@@ -673,8 +673,7 @@ where
self.build_byte_swap(value) self.build_byte_swap(value)
} }
AddressSpace::TransientStorage => todo!(), AddressSpace::Storage | AddressSpace::TransientStorage => {
AddressSpace::Storage => {
let storage_key_value = self.builder().build_ptr_to_int( let storage_key_value = self.builder().build_ptr_to_int(
pointer.value, pointer.value,
self.word_type(), self.word_type(),
@@ -692,9 +691,12 @@ where
let (storage_value_pointer, storage_value_length_pointer) = self let (storage_value_pointer, storage_value_length_pointer) = self
.build_stack_parameter(revive_common::BIT_LENGTH_WORD, "storage_value_pointer"); .build_stack_parameter(revive_common::BIT_LENGTH_WORD, "storage_value_pointer");
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call( self.build_runtime_call(
runtime_api::imports::GET_STORAGE, runtime_api::imports::GET_STORAGE,
&[ &[
self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(), storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(), self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer.to_int(self).into(), storage_value_pointer.to_int(self).into(),
@@ -758,8 +760,7 @@ where
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid"); .expect("Alignment is valid");
} }
AddressSpace::TransientStorage => todo!(), AddressSpace::Storage | AddressSpace::TransientStorage => {
AddressSpace::Storage => {
assert_eq!( assert_eq!(
value.as_basic_value_enum().get_type(), value.as_basic_value_enum().get_type(),
self.word_type().as_basic_type_enum() self.word_type().as_basic_type_enum()
@@ -789,9 +790,12 @@ where
self.builder() self.builder()
.build_store(storage_value_pointer.value, value)?; .build_store(storage_value_pointer.value, value)?;
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call( self.build_runtime_call(
runtime_api::imports::SET_STORAGE, runtime_api::imports::SET_STORAGE,
&[ &[
self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(), storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(), self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer_casted.into(), storage_value_pointer_casted.into(),
@@ -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(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) POLKAVM_IMPORT(uint32_t, clear_storage, uint32_t, uint32_t)
@@ -742,12 +742,21 @@ where
.map(|_| None) .map(|_| None)
} }
InstructionName::TLOAD => { InstructionName::TLOAD => {
let _arguments = self.pop_arguments_llvm(context); let arguments = self.pop_arguments_llvm(context);
anyhow::bail!("The `TLOAD` instruction is not supported until zkVM v1.5.0"); revive_llvm_context::polkavm_evm_storage::transient_load(
context,
arguments[0].into_int_value(),
)
.map(Some)
} }
InstructionName::TSTORE => { InstructionName::TSTORE => {
let _arguments = self.pop_arguments_llvm(context); let arguments = self.pop_arguments_llvm(context);
anyhow::bail!("The `TSTORE` instruction is not supported until zkVM v1.5.0"); revive_llvm_context::polkavm_evm_storage::transient_store(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(|_| None)
} }
InstructionName::PUSHIMMUTABLE => { InstructionName::PUSHIMMUTABLE => {
let key = self let key = self
@@ -485,18 +485,21 @@ impl FunctionCall {
.map(|_| None) .map(|_| None)
} }
Name::TLoad => { Name::TLoad => {
let _arguments = self.pop_arguments_llvm::<D, 1>(context)?; let arguments = self.pop_arguments_llvm::<D, 1>(context)?;
anyhow::bail!( revive_llvm_context::polkavm_evm_storage::transient_load(
"{} The `TLOAD` instruction is not supported until zkVM v1.5.0", context,
location arguments[0].into_int_value(),
); )
.map(Some)
} }
Name::TStore => { Name::TStore => {
let _arguments = self.pop_arguments_llvm::<D, 2>(context)?; let arguments = self.pop_arguments_llvm::<D, 2>(context)?;
anyhow::bail!( revive_llvm_context::polkavm_evm_storage::transient_store(
"{} The `TSTORE` instruction is not supported until zkVM v1.5.0", context,
location arguments[0].into_int_value(),
); arguments[1].into_int_value(),
)
.map(|_| None)
} }
Name::LoadImmutable => todo!(), Name::LoadImmutable => todo!(),
Name::SetImmutable => { Name::SetImmutable => {