Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: minor
- name: pallet-revive
bump: minor
- name: pallet-revive-uapi
bump: minor
21 changes: 21 additions & 0 deletions substrate/frame/revive/fixtures/contracts/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,25 @@ pub extern "C" fn call() {
assert_eq!(existing, None);
unwrap_output!(val, [0u8; 32], api::take_storage, StorageFlags::empty(), &KEY);
assert_eq!(**val, VALUE_3);

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);
unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::empty(), &KEY);
assert_eq!(**val, VALUE_A);

let mut stored: [u8; 32] = [0u8; 32];
let _ = 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];
let _ = api::get_storage_or_zero(StorageFlags::empty(), &KEY, &mut cleared);
assert_eq!(cleared, ZERO);
}
45 changes: 45 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,49 @@ pub mod env {
already_charged,
)?)
}

/// Sets the storage at a fixed 256-bit key with a fixed 256-bit value.
/// If the provided value is all zeros then the key is cleared (i.e. deleted),
/// similar to Ethereum’s SSTORE semantics.
#[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(value_ptr, 32)?;
if value.iter().all(|&b| b == 0) {
self.clear_storage(memory, flags, key_ptr, 32)
} else {
self.set_storage(memory, flags, key_ptr, 32, value_ptr, 32)
}
}

/// Reads the storage at a fixed 256-bit key and writes back a fixed 256-bit value.
/// If the key does not exist, writes back 32 bytes of 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, 32)?;
let value_opt = self.ext.get_storage(&key);
let value = match value_opt {
Some(v) => v,
None => 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(())
}
}
9 changes: 9 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,15 @@ 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 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.
fn set_storage_or_clear(flags: StorageFlags, key: &[u8; 32], value: &[u8; 32]) -> Option<u32>;

/// Retrieves the storage value for a fixed 256‑bit key.
/// If the key does not exist, the output buffer is filled with 32 zero bytes.
fn get_storage_or_zero(flags: StorageFlags, key: &[u8; 32], output: &mut [u8; 32]) -> Result;

/// Stores the value transferred along with this call/instantiate into the supplied buffer.
///
/// # Parameters
Expand Down
31 changes: 31 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) -> ReturnCode;
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,31 @@ 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(), // key is expected to be 32 bytes
encoded_value.as_ptr(), // value is expected to be 32 bytes
)
};
ret_code.into()
}

fn get_storage_or_zero(_flags: StorageFlags, key: &[u8; 32], output: &mut [u8; 32]) -> Result {
let ret_code = unsafe {
sys::get_storage_or_zero(
key.as_ptr(), // key (32 bytes)
output.as_mut_ptr(), // pointer where 32 bytes are written
)
};
ret_code.into()
}

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