Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions prdoc/pr_9759.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
title: Add set_storage set_storage_var_key methods
doc:
- audience: Runtime Dev
description: |-
Adds pallet revive methods for manipulating a contracts storage.

Fixes: https://github.com/paritytech/foundry-polkadot/issues/275
crates:
- name: pallet-revive
bump: major
48 changes: 48 additions & 0 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,54 @@ where
Ok(maybe_value)
}

/// Set storage of a specified contract under a specified key.
///
/// If the `value` is `None`, the storage entry is deleted.
///
/// Returns an error if the contract does not exist or if the write operation fails.
///
/// # Warning
///
/// Does not collect any storage deposit. Not safe to be called by user controlled code.
pub fn set_storage(address: H160, key: [u8; 32], value: Option<Vec<u8>>) -> SetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;

contract_info
.write(&Key::from_fixed(key), value, None, false)
.map_err(ContractAccessError::StorageWriteFailed)
}

/// Set the storage of a specified contract under a specified variable-sized key.
///
/// If the `value` is `None`, the storage entry is deleted.
///
/// Returns an error if the contract does not exist, if the key decoding fails,
/// or if the write operation fails.
///
/// # Warning
///
/// Does not collect any storage deposit. Not safe to be called by user controlled code.
pub fn set_storage_var_key(
address: H160,
key: Vec<u8>,
value: Option<Vec<u8>>,
) -> SetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;

contract_info
.write(
&Key::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
value,
None,
false,
)
.map_err(ContractAccessError::StorageWriteFailed)
}

/// Uploads new code and returns the Vm binary contract blob and deposit amount collected.
fn try_upload_pvm_code(
origin: T::AccountId,
Expand Down
7 changes: 6 additions & 1 deletion substrate/frame/revive/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

//! A crate that hosts a common definitions that are relevant for the pallet-revive.

use crate::{BalanceOf, Config, H160, U256};
use crate::{storage::WriteOutcome, BalanceOf, Config, H160, U256};
use alloc::{string::String, vec::Vec};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::weights::Weight;
Expand Down Expand Up @@ -198,13 +198,18 @@ pub type CodeUploadResult<Balance> = Result<CodeUploadReturnValue<Balance>, Disp
/// Result type of a `get_storage` call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;

/// Result type of a `set_storage` call.
pub type SetStorageResult = Result<WriteOutcome, ContractAccessError>;

/// The possible errors that can happen querying the storage of a contract.
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub enum ContractAccessError {
/// The given address doesn't point to a contract.
DoesntExist,
/// Storage key cannot be decoded from the provided input data.
KeyDecodingFailed,
/// Writing to storage failed.
StorageWriteFailed(DispatchError),
}

/// Output of a contract call or instantiation which ran to completion.
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ impl<T: Config> ContractInfo<T> {
}

/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`].
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum WriteOutcome {
/// No value existed at the specified key.
New,
Expand Down
87 changes: 86 additions & 1 deletion substrate/frame/revive/src/tests/pvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{
sol_data::{Bool, FixedBytes},
SolType,
},
storage::DeletionQueueManager,
storage::{DeletionQueueManager, WriteOutcome},
test_utils::builder::Contract,
tests::{
builder, initialize_block, test_utils::*, Balances, CodeHashLockupDepositPercent,
Expand Down Expand Up @@ -5063,3 +5063,88 @@ fn reject_signed_tx_from_builtin_precompile_address() {
assert_err!(instantiate_result.result, DispatchError::BadOrigin);
});
}

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

ExtBuilder::default().build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);

let Contract { addr, .. } =
builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract();

let contract_key_to_test = [1; 32];
// Checking non-existing keys gets created.
let storage_value = Pallet::<Test>::get_storage(addr, contract_key_to_test).unwrap();
assert_eq!(storage_value, None);

let value_to_write = Some(vec![1, 2, 3]);
let write_result =
Pallet::<Test>::set_storage(addr, contract_key_to_test, value_to_write.clone())
.unwrap();
assert_eq!(write_result, WriteOutcome::New);
let storage_value = Pallet::<Test>::get_storage(addr, contract_key_to_test).unwrap();
assert_eq!(storage_value, value_to_write);

// Check existing keys overwrite

let new_value_to_write = Some(vec![5, 1, 2, 3]);
let write_result =
Pallet::<Test>::set_storage(addr, contract_key_to_test, new_value_to_write.clone())
.unwrap();
assert_eq!(
write_result,
WriteOutcome::Overwritten(value_to_write.map(|v| v.len()).unwrap_or_default() as u32)
);
let storage_value = Pallet::<Test>::get_storage(addr, contract_key_to_test).unwrap();
assert_eq!(storage_value, new_value_to_write);
});
}

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

ExtBuilder::default().build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);

let Contract { addr, .. } =
builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract();

let contract_key_to_test = vec![1; 85];
// Checking non-existing keys gets created.
let storage_value =
Pallet::<Test>::get_storage_var_key(addr, contract_key_to_test.clone()).unwrap();
assert_eq!(storage_value, None);

let value_to_write = Some(vec![1, 2, 3]);
let write_result = Pallet::<Test>::set_storage_var_key(
addr,
contract_key_to_test.clone(),
value_to_write.clone(),
)
.unwrap();
assert_eq!(write_result, WriteOutcome::New);
let storage_value =
Pallet::<Test>::get_storage_var_key(addr, contract_key_to_test.clone()).unwrap();
assert_eq!(storage_value, value_to_write);

// Check existing keys overwrite

let new_value_to_write = Some(vec![5, 1, 2, 3]);
let write_result = Pallet::<Test>::set_storage_var_key(
addr,
contract_key_to_test.clone(),
new_value_to_write.clone(),
)
.unwrap();
assert_eq!(
write_result,
WriteOutcome::Overwritten(value_to_write.map(|v| v.len()).unwrap_or_default() as u32)
);
let storage_value =
Pallet::<Test>::get_storage_var_key(addr, contract_key_to_test.clone()).unwrap();
assert_eq!(storage_value, new_value_to_write);
});
}
Loading