Skip to content

Commit b5b9782

Browse files
smiasojedpgherveouathei
authored andcommitted
[pallet_contracts] Add support for transient storage in contracts host functions (paritytech#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 <pgherveou@gmail.com> Co-authored-by: Alexander Theißen <alex.theissen@me.com>
1 parent d3204cb commit b5b9782

19 files changed

Lines changed: 2943 additions & 396 deletions

File tree

cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ impl Config for Runtime {
6565
type AddressGenerator = DefaultAddressGenerator;
6666
type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
6767
type MaxStorageKeyLen = ConstU32<128>;
68+
type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>;
6869
type UnsafeUnstableInterface = ConstBool<true>;
6970
type UploadOrigin = EnsureSigned<Self::AccountId>;
7071
type InstantiateOrigin = EnsureSigned<Self::AccountId>;

prdoc/pr_4566.prdoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
2+
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
3+
4+
title: "[pallet_contracts] Add support for transient storage in contracts host functions"
5+
6+
doc:
7+
- audience: Runtime User
8+
description: |
9+
This PR implements transient storage, which behaves identically to regular storage
10+
but is kept only in memory and discarded after every transaction.
11+
This functionality is similar to the `TSTORE` and `TLOAD` operations used in Ethereum.
12+
The following new host functions have been introduced: `get_transient_storage`,
13+
`set_transient_storage`, `take_transient_storage`, `clear_transient_storage` and
14+
`contains_transient_storage`.
15+
These functions are declared as unstable and thus are not activated.
16+
17+
crates:
18+
- name: pallet-contracts
19+
bump: major
20+
- name: pallet-contracts-uapi
21+
bump: major
22+
- name: contracts-rococo-runtime
23+
bump: minor

substrate/bin/node/runtime/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,7 @@ impl pallet_contracts::Config for Runtime {
13731373
type UploadOrigin = EnsureSigned<Self::AccountId>;
13741374
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
13751375
type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
1376+
type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>;
13761377
type RuntimeHoldReason = RuntimeHoldReason;
13771378
#[cfg(not(feature = "runtime-benchmarks"))]
13781379
type Migrations = ();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! This calls another contract as passed as its account id. It also creates some transient storage.
19+
#![no_std]
20+
#![no_main]
21+
22+
use common::input;
23+
use uapi::{HostFn, HostFnImpl as api};
24+
25+
#[no_mangle]
26+
#[polkavm_derive::polkavm_export]
27+
pub extern "C" fn deploy() {}
28+
29+
#[no_mangle]
30+
#[polkavm_derive::polkavm_export]
31+
pub extern "C" fn call() {
32+
input!(
33+
buffer,
34+
len: u32,
35+
input: [u8; 4],
36+
callee: [u8; 32],
37+
);
38+
39+
let data = [0u8; 16 * 1024];
40+
let value = &data[..len as usize];
41+
#[allow(deprecated)]
42+
api::set_transient_storage(buffer, value);
43+
44+
// Call the callee
45+
api::call_v2(
46+
uapi::CallFlags::empty(),
47+
callee,
48+
0u64, // How much ref_time weight to devote for the execution. 0 = all.
49+
0u64, // How much proof_size weight to devote for the execution. 0 = all.
50+
None,
51+
&0u64.to_le_bytes(), // Value transferred to the contract.
52+
input,
53+
None,
54+
)
55+
.unwrap();
56+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#![no_std]
19+
#![no_main]
20+
21+
use common::input;
22+
use uapi::{HostFn, HostFnImpl as api};
23+
24+
#[no_mangle]
25+
#[polkavm_derive::polkavm_export]
26+
pub extern "C" fn deploy() {}
27+
28+
#[no_mangle]
29+
#[polkavm_derive::polkavm_export]
30+
pub extern "C" fn call() {
31+
input!(len: u32, );
32+
33+
let buffer = [0u8; 16 * 1024];
34+
let data = &buffer[..len as usize];
35+
36+
// Place a garbage value in the transient storage, with the size specified by the call input.
37+
let mut key = [0u8; 32];
38+
key[0] = 1;
39+
40+
#[allow(deprecated)]
41+
api::set_transient_storage(&key, data);
42+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! This contract tests the transient storage APIs.
19+
#![no_std]
20+
#![no_main]
21+
22+
use common::unwrap_output;
23+
use uapi::{HostFn, HostFnImpl as api};
24+
25+
#[no_mangle]
26+
#[polkavm_derive::polkavm_export]
27+
pub extern "C" fn deploy() {}
28+
29+
#[no_mangle]
30+
#[polkavm_derive::polkavm_export]
31+
pub extern "C" fn call() {
32+
const KEY: [u8; 32] = [1u8; 32];
33+
const VALUE_1: [u8; 4] = [1u8; 4];
34+
const VALUE_2: [u8; 4] = [2u8; 4];
35+
const VALUE_3: [u8; 4] = [3u8; 4];
36+
37+
#[allow(deprecated)]
38+
{
39+
let existing = api::set_transient_storage(&KEY, &VALUE_1);
40+
assert_eq!(existing, None);
41+
assert_eq!(api::contains_transient_storage(&KEY), Some(VALUE_1.len() as _));
42+
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
43+
assert_eq!(**val, VALUE_1);
44+
45+
let existing = api::set_transient_storage(&KEY, &VALUE_2);
46+
assert_eq!(existing, Some(VALUE_1.len() as _));
47+
unwrap_output!(val, [0u8; 4], api::get_transient_storage, &KEY);
48+
assert_eq!(**val, VALUE_2);
49+
50+
api::clear_transient_storage(&KEY);
51+
assert_eq!(api::contains_transient_storage(&KEY), None);
52+
53+
let existing = api::set_transient_storage(&KEY, &VALUE_3);
54+
assert_eq!(existing, None);
55+
unwrap_output!(val, [0u8; 32], api::take_transient_storage, &KEY);
56+
assert_eq!(**val, VALUE_3);
57+
}
58+
}

substrate/frame/contracts/src/benchmarking/call_builder.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717

1818
use crate::{
1919
benchmarking::{Contract, WasmModule},
20-
exec::Stack,
20+
exec::{Ext, Key, Stack},
2121
storage::meter::Meter,
22+
transient_storage::MeterEntry,
2223
wasm::Runtime,
23-
BalanceOf, Config, DebugBufferVec, Determinism, ExecReturnValue, GasMeter, Origin, Schedule,
24-
TypeInfo, WasmBlob, Weight,
24+
BalanceOf, Config, DebugBufferVec, Determinism, Error, ExecReturnValue, GasMeter, Origin,
25+
Schedule, TypeInfo, WasmBlob, Weight,
2526
};
2627
use alloc::{vec, vec::Vec};
2728
use codec::{Encode, HasCompact};
@@ -56,6 +57,7 @@ pub struct CallSetup<T: Config> {
5657
debug_message: Option<DebugBufferVec<T>>,
5758
determinism: Determinism,
5859
data: Vec<u8>,
60+
transient_storage_size: u32,
5961
}
6062

6163
impl<T> Default for CallSetup<T>
@@ -103,6 +105,7 @@ where
103105
debug_message: None,
104106
determinism: Determinism::Enforced,
105107
data: vec![],
108+
transient_storage_size: 0,
106109
}
107110
}
108111

@@ -126,6 +129,11 @@ where
126129
self.data = value;
127130
}
128131

132+
/// Set the transient storage size.
133+
pub fn set_transient_storage_size(&mut self, size: u32) {
134+
self.transient_storage_size = size;
135+
}
136+
129137
/// Set the debug message.
130138
pub fn enable_debug_message(&mut self) {
131139
self.debug_message = Some(Default::default());
@@ -148,7 +156,7 @@ where
148156

149157
/// Build the call stack.
150158
pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob<T>) {
151-
StackExt::bench_new_call(
159+
let mut ext = StackExt::bench_new_call(
152160
self.dest.clone(),
153161
self.origin.clone(),
154162
&mut self.gas_meter,
@@ -157,7 +165,11 @@ where
157165
self.value,
158166
self.debug_message.as_mut(),
159167
self.determinism,
160-
)
168+
);
169+
if self.transient_storage_size > 0 {
170+
Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
171+
}
172+
ext
161173
}
162174

163175
/// Prepare a call to the module.
@@ -169,6 +181,30 @@ where
169181
let (func, store) = module.bench_prepare_call(ext, input);
170182
PreparedCall { func, store }
171183
}
184+
185+
/// Add transient_storage
186+
fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
187+
let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
188+
ext.transient_storage().meter().current_mut().limit = size;
189+
for i in 1u32.. {
190+
let mut key_data = i.to_le_bytes().to_vec();
191+
while key_data.last() == Some(&0) {
192+
key_data.pop();
193+
}
194+
let key = Key::<T>::try_from_var(key_data).unwrap();
195+
if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
196+
// Restore previous settings.
197+
ext.transient_storage().meter().current_mut().limit = limit;
198+
ext.transient_storage().meter().current_mut().amount = amount;
199+
if e == Error::<T>::OutOfTransientStorage.into() {
200+
break;
201+
} else {
202+
return Err("Initialization of the transient storage failed");
203+
}
204+
}
205+
}
206+
Ok(())
207+
}
172208
}
173209

174210
#[macro_export]

0 commit comments

Comments
 (0)