Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions prdoc/pr_7857.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
title: Add new host APIs set_storage_or_clear and get_storage_or_zero
doc:
- audience: Runtime Dev
description: "# Description\n\n*This PR introduces two new storage API functions\u2014\
set_storage_or_clear and get_storage_or_zero\u2014which provide fixed\u2011size\
\ (32\u2011byte) storage operations. These APIs are an attempt to match Ethereum\u2019\
s SSTORE semantics. These APIs provide additional functionality for setting and\
\ retrieving storage values and clearing storage when a zero value is provided\
\ and returning zero bytes when a key does not exist.*\n\nFixes #6944\n## Review\
\ Notes\n\n* Changes in `runtime.rs`\nAdded the set_storage_or_clear function\
\ to set storage at a fixed 256-bit key with a fixed 256-bit value. If the provided\
\ value is all zeros, the key is cleared.\nAdded the get_storage_or_zero function\
\ to read storage at a fixed 256-bit key and write back a fixed 256-bit value.\
\ If the key does not exist, 32 bytes of zero are written back.\n* Changes in\
\ `storage.rs`\nAdded test cases to cover the new set_storage_or_clear and get_storage_or_zero\
\ APIs.\n.\n\n```\n// Example usage of the new set_storage_or_clear function\n\
let existing = api::set_storage_or_clear(StorageFlags::empty(), &KEY, &VALUE_A);\n\
assert_eq!(existing, None);\n\n// Example usage of the new get_storage_or_zero\
\ function\nlet mut stored: [u8; 32] = [0u8; 32];\nlet _ = api::get_storage_or_zero(StorageFlags::empty(),\
\ &KEY, &mut stored);\nassert_eq!(stored, VALUE_A);\n```\n\n*All existing tests\
\ pass*\n\n# Checklist\n\n* [x] My PR includes a detailed description as outlined\
\ in the \"Description\" and its two subsections above.\n* [ ] My PR follows the\
\ [labeling requirements](\nhttps://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process\n\
) of this project (at minimum one label for `T` required)\n * External contributors:\
\ ask maintainers to put the right label on your PR.\n* [x] I have made corresponding\
\ changes to the documentation (if applicable)\n* [x] I have added tests that\
\ prove my fix is effective or that my feature works (if applicable)"
crates:
- name: pallet-revive-fixtures
bump: patch
- name: pallet-revive
bump: patch
- name: pallet-revive-uapi
bump: minor
55 changes: 55 additions & 0 deletions substrate/frame/revive/fixtures/contracts/storage_clear.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 storage APIs. It sets and clears storage values using the different
//! versions of the storage APIs.
#![no_std]
#![no_main]

#[allow(unused_imports)]
use common::unwrap_output;
use uapi::{HostFn, HostFnImpl as api, StorageFlags};

#[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_A: [u8; 32] = [4u8; 32];
const ZERO: [u8; 32] = [0u8; 32];

api::clear_storage(StorageFlags::empty(), &KEY);

assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), None);

let existing = api::set_storage_or_clear(StorageFlags::empty(), &KEY, &VALUE_A);
assert_eq!(existing, None);

let mut stored: [u8; 32] = [0u8; 32];
api::get_storage_or_zero(StorageFlags::empty(), &KEY, &mut stored);
assert_eq!(stored, VALUE_A);

let existing = api::set_storage_or_clear(StorageFlags::empty(), &KEY, &ZERO);
assert_eq!(existing, Some(32));

let mut cleared: [u8; 32] = [1u8; 32];
api::get_storage_or_zero(StorageFlags::empty(), &KEY, &mut cleared);
assert_eq!(cleared, ZERO);
}
15 changes: 15 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,21 @@ fn storage_max_value_limit() {
});
}

#[test]
fn storage_clear() {
let (code, _code_hash) = compile_module("storage_clear").unwrap();

ExtBuilder::default().build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
let min_balance = Contracts::min_balance();
let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code))
.value(min_balance * 100)
.build_and_unwrap_contract();

builder::bare_call(addr).build_and_unwrap_result();
});
}

#[test]
fn transient_storage_work() {
let (code, _code_hash) = compile_module("transient_storage").unwrap();
Expand Down
40 changes: 40 additions & 0 deletions substrate/frame/revive/src/wasm/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2152,4 +2152,44 @@ pub mod env {
already_charged,
)?)
}

/// Sets the storage at a fixed 256-bit key with a fixed 256-bit value.
/// See [`pallet_revive_uapi::HostFn::set_storage_or_clear`].
#[stable]
#[mutating]
fn set_storage_or_clear(
&mut self,
memory: &mut M,
flags: u32,
key_ptr: u32,
value_ptr: u32,
) -> Result<u32, TrapReason> {
let value = memory.read_h256(value_ptr)?;
if value.as_bytes().iter().all(|&b| b == 0) {
self.clear_storage(memory, flags, key_ptr, SENTINEL)
} else {
self.set_storage(memory, flags, key_ptr, SENTINEL, value_ptr, 32)
}
}

/// Reads the storage at a fixed 256-bit key and writes back a fixed 256-bit value.
/// See [`pallet_revive_uapi::HostFn::get_storage_or_zero`].
#[stable]
fn get_storage_or_zero(
&mut self,
memory: &mut M,
key_ptr: u32,
out_ptr: u32,
) -> Result<(), TrapReason> {
let key = self.decode_key(memory, key_ptr, SENTINEL)?;
let value = self.ext.get_storage(&key).unwrap_or_else(|| vec![0u8; 32]);
let mut fixed_value = [0u8; 32];
if value.len() >= 32 {
fixed_value.copy_from_slice(&value[..32]);
} else {
fixed_value[..value.len()].copy_from_slice(&value);
}
memory.write(out_ptr, &fixed_value)?;
Ok(())
}
}
22 changes: 22 additions & 0 deletions substrate/frame/revive/uapi/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,28 @@ pub trait HostFn: private::Sealed {
/// Returns the size of the pre-existing value at the specified key if any.
fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option<u32>;

/// Sets the storage entry for a fixed 256‑bit key with a fixed 256‑bit value.
///
/// If the provided 32‑byte value is all zeros then the key is cleared (i.e. deleted),
/// mimicking Ethereum’s SSTORE behavior.
///
/// # Parameters
/// - `key`: The fixed 256‑bit storage key (32 bytes).
/// - `value`: The fixed 256‑bit storage value (32 bytes).
///
/// # Return
/// Returns the size (in bytes) of the pre‑existing value at the specified key, if any.
fn set_storage_or_clear(flags: StorageFlags, key: &[u8; 32], value: &[u8; 32]) -> Option<u32>;

/// Retrieves the storage entry for a fixed 256‑bit key.
///
/// If the key does not exist, the output buffer is filled with 32 zero bytes.
///
/// # Parameters
/// - `key`: The fixed 256‑bit storage key (32 bytes).
/// - `output`: A mutable output buffer (32 bytes) where the storage entry is written.
fn get_storage_or_zero(flags: StorageFlags, key: &[u8; 32], output: &mut [u8; 32]);

/// Stores the value transferred along with this call/instantiate into the supplied buffer.
///
/// # Parameters
Expand Down
21 changes: 21 additions & 0 deletions substrate/frame/revive/uapi/src/host/riscv64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ mod sys {
value_ptr: *const u8,
value_len: u32,
) -> ReturnCode;
pub fn set_storage_or_clear(
flags: u32,
key_ptr: *const u8,
value_ptr: *const u8,
) -> ReturnCode;
pub fn clear_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode;
pub fn get_storage(
flags: u32,
Expand All @@ -51,6 +56,7 @@ mod sys {
out_ptr: *mut u8,
out_len_ptr: *mut u32,
) -> ReturnCode;
pub fn get_storage_or_zero(key_ptr: *const u8, out_ptr: *mut u8);
pub fn contains_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode;
pub fn take_storage(
flags: u32,
Expand Down Expand Up @@ -314,6 +320,21 @@ impl HostFn for HostFnImpl {
ret_code.into()
}

fn set_storage_or_clear(
flags: StorageFlags,
key: &[u8; 32],
encoded_value: &[u8; 32],
) -> Option<u32> {
let ret_code = unsafe {
sys::set_storage_or_clear(flags.bits(), key.as_ptr(), encoded_value.as_ptr())
};
ret_code.into()
}

fn get_storage_or_zero(_flags: StorageFlags, key: &[u8; 32], output: &mut [u8; 32]) {
unsafe { sys::get_storage_or_zero(key.as_ptr(), output.as_mut_ptr()) };
}

fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result {
let mut output_len = output.len() as u32;
let ret_code = {
Expand Down
Loading