Skip to content

Commit

Permalink
[pallet_contracts] Add support for transient storage in contracts hos…
Browse files Browse the repository at this point in the history
…t functions (#4566)

Introduce transient storage, which behaves identically to regular
storage but is kept only in memory and discarded after every
transaction. This functionality is similar to the `TSTORE` and `TLOAD`
operations used in Ethereum.

The following new host functions have been introduced:
`get_transient_storage`
`set_transient_storage`
`take_transient_storage`
`clear_transient_storage`
`contains_transient_storage`
Note: These functions are declared as `unstable` and thus are not
activated.

---------

Co-authored-by: command-bot <>
Co-authored-by: PG Herveou <[email protected]>
Co-authored-by: Alexander Theißen <[email protected]>
  • Loading branch information
3 people authored Jul 16, 2024
1 parent cde2eb4 commit eac2a22
Show file tree
Hide file tree
Showing 19 changed files with 2,943 additions and 396 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl Config for Runtime {
type AddressGenerator = DefaultAddressGenerator;
type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
type MaxStorageKeyLen = ConstU32<128>;
type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>;
type UnsafeUnstableInterface = ConstBool<true>;
type UploadOrigin = EnsureSigned<Self::AccountId>;
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
Expand Down
23 changes: 23 additions & 0 deletions prdoc/pr_4566.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "[pallet_contracts] Add support for transient storage in contracts host functions"

doc:
- audience: Runtime User
description: |
This PR implements transient storage, which behaves identically to regular storage
but is kept only in memory and discarded after every transaction.
This functionality is similar to the `TSTORE` and `TLOAD` operations used in Ethereum.
The following new host functions have been introduced: `get_transient_storage`,
`set_transient_storage`, `take_transient_storage`, `clear_transient_storage` and
`contains_transient_storage`.
These functions are declared as unstable and thus are not activated.

crates:
- name: pallet-contracts
bump: major
- name: pallet-contracts-uapi
bump: major
- name: contracts-rococo-runtime
bump: minor
1 change: 1 addition & 0 deletions substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,7 @@ impl pallet_contracts::Config for Runtime {
type UploadOrigin = EnsureSigned<Self::AccountId>;
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>;
type RuntimeHoldReason = RuntimeHoldReason;
#[cfg(not(feature = "runtime-benchmarks"))]
type Migrations = ();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This calls another contract as passed as its account id. It also creates some transient storage.
#![no_std]
#![no_main]

use common::input;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(
buffer,
len: u32,
input: [u8; 4],
callee: [u8; 32],
);

let data = [0u8; 16 * 1024];
let value = &data[..len as usize];
#[allow(deprecated)]
api::set_transient_storage(buffer, value);

// Call the callee
api::call_v2(
uapi::CallFlags::empty(),
callee,
0u64, // How much ref_time weight to devote for the execution. 0 = all.
0u64, // How much proof_size weight to devote for the execution. 0 = all.
None,
&0u64.to_le_bytes(), // Value transferred to the contract.
input,
None,
)
.unwrap();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![no_std]
#![no_main]

use common::input;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
input!(len: u32, );

let buffer = [0u8; 16 * 1024];
let data = &buffer[..len as usize];

// Place a garbage value in the transient storage, with the size specified by the call input.
let mut key = [0u8; 32];
key[0] = 1;

#[allow(deprecated)]
api::set_transient_storage(&key, data);
}
58 changes: 58 additions & 0 deletions substrate/frame/contracts/fixtures/contracts/transient_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! This contract tests the transient storage APIs.
#![no_std]
#![no_main]

use common::unwrap_output;
use uapi::{HostFn, HostFnImpl as api};

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[no_mangle]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
const KEY: [u8; 32] = [1u8; 32];
const VALUE_1: [u8; 4] = [1u8; 4];
const VALUE_2: [u8; 4] = [2u8; 4];
const VALUE_3: [u8; 4] = [3u8; 4];

#[allow(deprecated)]
{
let existing = api::set_transient_storage(&KEY, &VALUE_1);
assert_eq!(existing, None);
assert_eq!(api::contains_transient_storage(&KEY), Some(VALUE_1.len() as _));
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
assert_eq!(**val, VALUE_1);

let existing = api::set_transient_storage(&KEY, &VALUE_2);
assert_eq!(existing, Some(VALUE_1.len() as _));
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
assert_eq!(**val, VALUE_2);

api::clear_transient_storage(&KEY);
assert_eq!(api::contains_transient_storage(&KEY), None);

let existing = api::set_transient_storage(&KEY, &VALUE_3);
assert_eq!(existing, None);
unwrap_output!(val, [0u8; 32], api::take_transient_storage, &KEY);
assert_eq!(**val, VALUE_3);
}
}
46 changes: 41 additions & 5 deletions substrate/frame/contracts/src/benchmarking/call_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@

use crate::{
benchmarking::{Contract, WasmModule},
exec::Stack,
exec::{Ext, Key, Stack},
storage::meter::Meter,
transient_storage::MeterEntry,
wasm::Runtime,
BalanceOf, Config, DebugBufferVec, Determinism, ExecReturnValue, GasMeter, Origin, Schedule,
TypeInfo, WasmBlob, Weight,
BalanceOf, Config, DebugBufferVec, Determinism, Error, ExecReturnValue, GasMeter, Origin,
Schedule, TypeInfo, WasmBlob, Weight,
};
use alloc::{vec, vec::Vec};
use codec::{Encode, HasCompact};
Expand Down Expand Up @@ -56,6 +57,7 @@ pub struct CallSetup<T: Config> {
debug_message: Option<DebugBufferVec<T>>,
determinism: Determinism,
data: Vec<u8>,
transient_storage_size: u32,
}

impl<T> Default for CallSetup<T>
Expand Down Expand Up @@ -103,6 +105,7 @@ where
debug_message: None,
determinism: Determinism::Enforced,
data: vec![],
transient_storage_size: 0,
}
}

Expand All @@ -126,6 +129,11 @@ where
self.data = value;
}

/// Set the transient storage size.
pub fn set_transient_storage_size(&mut self, size: u32) {
self.transient_storage_size = size;
}

/// Set the debug message.
pub fn enable_debug_message(&mut self) {
self.debug_message = Some(Default::default());
Expand All @@ -148,7 +156,7 @@ where

/// Build the call stack.
pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob<T>) {
StackExt::bench_new_call(
let mut ext = StackExt::bench_new_call(
self.dest.clone(),
self.origin.clone(),
&mut self.gas_meter,
Expand All @@ -157,7 +165,11 @@ where
self.value,
self.debug_message.as_mut(),
self.determinism,
)
);
if self.transient_storage_size > 0 {
Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
}
ext
}

/// Prepare a call to the module.
Expand All @@ -169,6 +181,30 @@ where
let (func, store) = module.bench_prepare_call(ext, input);
PreparedCall { func, store }
}

/// Add transient_storage
fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
ext.transient_storage().meter().current_mut().limit = size;
for i in 1u32.. {
let mut key_data = i.to_le_bytes().to_vec();
while key_data.last() == Some(&0) {
key_data.pop();
}
let key = Key::<T>::try_from_var(key_data).unwrap();
if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
// Restore previous settings.
ext.transient_storage().meter().current_mut().limit = limit;
ext.transient_storage().meter().current_mut().amount = amount;
if e == Error::<T>::OutOfTransientStorage.into() {
break;
} else {
return Err("Initialization of the transient storage failed");
}
}
}
Ok(())
}
}

#[macro_export]
Expand Down
Loading

0 comments on commit eac2a22

Please sign in to comment.