Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3cc8d6d
add external transient_storage to pallet_revive::ExecConfig
pkhry Nov 30, 2025
fdba022
Update from github-actions[bot] running command 'prdoc'
github-actions[bot] Dec 1, 2025
093782d
prdoc
pkhry Dec 2, 2025
d9f4b93
merge fix
pkhry Dec 2, 2025
8becba6
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 4, 2025
12d6c8b
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 4, 2025
d31790d
up
pkhry Dec 2, 2025
fc09d1d
Update substrate/frame/revive/src/primitives.rs
pkhry Dec 5, 2025
d37a69b
rm spurious commas
pkhry Dec 5, 2025
561cdef
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 5, 2025
cc82a2b
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 8, 2025
de4d819
address comments
pkhry Dec 9, 2025
840ad71
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Dec 9, 2025
68b627c
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 9, 2025
d70b9d0
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 10, 2025
75f36cf
Merge branch 'master' into pkhry/external_transient_storage
pkhry Dec 13, 2025
a97d852
Merge remote-tracking branch 'origin/master' into pkhry/external_tran…
pkhry Jan 22, 2026
f5a7e42
review comments
pkhry Jan 22, 2026
e27ed4d
fix compilation
pkhry Jan 22, 2026
e00b2c1
cleanup builder change
pkhry Jan 22, 2026
3d87b31
fix erc20 transactor calls
pkhry Jan 23, 2026
8ca6e61
fixup tests
pkhry Jan 25, 2026
037c675
clippy
pkhry Jan 25, 2026
8e35cc9
bare_instantiate
pkhry Jan 25, 2026
28d5d53
Merge branch 'master' into pkhry/external_transient_storage
pkhry Jan 25, 2026
fc0394d
Merge branch 'master' into pkhry/external_transient_storage
pkhry Jan 26, 2026
eaae888
fix prdoc
pkhry Jan 27, 2026
2cdbab6
Merge branch 'master' into pkhry/external_transient_storage
pkhry Jan 29, 2026
6b2dea7
Merge branch 'master' into pkhry/external_transient_storage
pkhry Jan 30, 2026
8f0cbcc
Update from github-actions[bot] running command 'fmt'
github-actions[bot] Jan 30, 2026
469b6ba
revert breaking change
pkhry Jan 30, 2026
5f097b1
upd more
pkhry Jan 30, 2026
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
12 changes: 12 additions & 0 deletions prdoc/pr_10493.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
title: add external transient_storage to pallet_revive::ExecConfig
doc:
- audience: Node Dev
description: |-
# Description

This PR adds the ability to supply external copy of `TransientStorage` to `pallet_revive::ExecConfig` to be used during execution.
This is required by testing in foundry as we only enter `pallet_revive` during a `CALL` or `CREATE` instruction and we need to carryover
the transient storage to other following calls as they happen within an external tx
crates:
- name: pallet-revive
bump: major
50 changes: 36 additions & 14 deletions substrate/frame/revive/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,9 @@ where
*caller_frame = Default::default();
}

self.transient_storage.start_transaction();
self.with_transient_storage(|transient_storage| {
transient_storage.start_transaction();
});
let is_first_frame = self.frames.is_empty();

let do_transaction = || -> ExecResult {
Expand Down Expand Up @@ -1495,12 +1497,13 @@ where
(false, Err(error.into()))
},
};

if success {
self.transient_storage.commit_transaction();
} else {
self.transient_storage.rollback_transaction();
}
self.with_transient_storage(|transient_storage| {
if success {
transient_storage.commit_transaction();
} else {
transient_storage.rollback_transaction();
}
});
log::trace!(target: LOG_TARGET, "frame finished with: {output:?}");

self.pop_frame(success);
Expand Down Expand Up @@ -1630,11 +1633,11 @@ where
let value = BalanceWithDust::<BalanceOf<T>>::from_value::<T>(value)
.map_err(|_| Error::<T>::BalanceConversionFailed)?;
if value.is_zero() {
return Ok(());
return Ok(())
}

if <System<T>>::account_exists(to) {
return transfer_with_dust::<T>(from, to, value, preservation)
return transfer_with_dust::<T>(from, to, value, preservation);
}

let origin = origin.account_id()?;
Expand Down Expand Up @@ -1840,6 +1843,21 @@ where
}
true
}

fn with_transient_storage<R, F: FnOnce(&mut TransientStorage<T>) -> R>(&mut self, f: F) -> R {
if let Some(transient) = &self.exec_config.transient_storage {
f(&mut transient.borrow_mut())
} else {
f(&mut self.transient_storage)
}
}
fn with_transient_storage_ref<R, F: FnOnce(&TransientStorage<T>) -> R>(&self, f: F) -> R {
if let Some(transient) = &self.exec_config.transient_storage {
f(&transient.borrow())
} else {
f(&self.transient_storage)
}
}
}

impl<'a, T, E> Ext for Stack<'a, T, E>
Expand Down Expand Up @@ -2172,13 +2190,15 @@ where
}

fn get_transient_storage(&self, key: &Key) -> Option<Vec<u8>> {
self.transient_storage.read(self.account_id(), key)
self.with_transient_storage_ref(|transient_storage| {
transient_storage.read(self.account_id(), key)
})
}

fn get_transient_storage_size(&self, key: &Key) -> Option<u32> {
self.transient_storage
.read(self.account_id(), key)
.map(|value| value.len() as _)
self.with_transient_storage_ref(|transient_storage| {
transient_storage.read(self.account_id(), key).map(|value| value.len() as _)
})
}

fn set_transient_storage(
Expand All @@ -2188,7 +2208,9 @@ where
take_old: bool,
) -> Result<WriteOutcome, DispatchError> {
let account_id = self.account_id().clone();
self.transient_storage.write(&account_id, key, value, take_old)
self.with_transient_storage(|transient_storage| {
transient_storage.write(&account_id, key, value, take_old)
})
}

fn account_id(&self) -> &T::AccountId {
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,14 @@ pub use crate::{
ReceiptInfo,
},
exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin},
limits::TRANSIENT_STORAGE_BYTES as TRANSIENT_STORAGE_LIMIT,
metering::{
EthTxInfo, FrameMeter, ResourceMeter, Token as WeightToken, TransactionLimits,
TransactionMeter,
},
pallet::{genesis, *},
storage::{AccountInfo, ContractInfo},
transient_storage::{MeterEntry, StorageMeter as TransientStorageMeter, TransientStorage},
vm::{BytecodeType, ContractBlob},
};
pub use codec;
Expand Down
15 changes: 12 additions & 3 deletions substrate/frame/revive/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
//! A crate that hosts a common definitions that are relevant for the pallet-revive.

use crate::{
evm::DryRunConfig, mock::MockHandler, storage::WriteOutcome, BalanceOf, Config, Time, H160,
U256,
evm::DryRunConfig, mock::MockHandler, storage::WriteOutcome,
transient_storage::TransientStorage, BalanceOf, Config, Time, H160, U256,
};
use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec};
use alloc::{boxed::Box, fmt::Debug, rc::Rc, string::String, vec::Vec};
use codec::{Decode, Encode, MaxEncodedLen};
use core::cell::RefCell;
use frame_support::{traits::tokens::Balance, weights::Weight};
use pallet_revive_uapi::ReturnFlags;
use scale_info::TypeInfo;
Expand Down Expand Up @@ -391,6 +392,10 @@ pub struct ExecConfig<T: Config> {
/// This is primarily used for testing purposes and should be `None` in production
/// environments.
pub mock_handler: Option<Box<dyn MockHandler<T>>>,
/// External transient storage useful for testing.
///
/// Should be `None` in production environments.
pub transient_storage: Option<Rc<RefCell<TransientStorage<T>>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move into the Stack using take() when the Stack is created.

Suggested change
pub transient_storage: Option<Rc<RefCell<TransientStorage<T>>>>,
pub initial_transient_storage: Option<TransientStorage<T>>>,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we still need to have it back in foundry, so moving into into the stack will not allow us to get the instance back

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh okay got it. But is the Rc needed? The calling code (foundry) owns TransientStorage and can just borrow when it wants to access it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to introduce lifetimes and possible refactor parts of the interface to avoid an RC. Rc is needed because ownership of ExecConfig goes directly to pallet-revive from the caller so it's a one way trip unless we return parts of ExecConfig at the end of the call if requested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rc is needed because ownership of ExecConfig goes directly to pallet-revive

Ahh okay. I thought it takes a reference. It should not take exec_config by value. This makes no sense as it is just forwarded as reference to Stack. So this should be refactored to get rid of the Rc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new life times. Just changing the signature of the bare_* calls to take ExecConfig by reference instead of value. This will not require any new explicit life time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cannot pass TransientStorage by reference inside ExecConfig without a lifetime, no? so this would cause us to add lifetime param to every callsite of ExecConfig and to its definition.

Anyways, i'll try and see whether the lifetimes would line up inside exec with it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we cannot pass TransientStorage by reference inside ExecConfig without a lifetime, no?

I am not asking you to do this. Pass ExecConfig as reference into bare_*. The TransientStorage is passed as RefCell<TransientStorage>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, ok

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

impl<T: Config> ExecConfig<T> {
Expand All @@ -402,6 +407,7 @@ impl<T: Config> ExecConfig<T> {
effective_gas_price: None,
is_dry_run: None,
mock_handler: None,
transient_storage: None,
}
}

Expand All @@ -412,6 +418,7 @@ impl<T: Config> ExecConfig<T> {
effective_gas_price: None,
mock_handler: None,
is_dry_run: None,
transient_storage: None,
}
}

Expand All @@ -423,6 +430,7 @@ impl<T: Config> ExecConfig<T> {
effective_gas_price: Some(effective_gas_price),
mock_handler: None,
is_dry_run: None,
transient_storage: None,
}
}

Expand All @@ -444,6 +452,7 @@ impl<T: Config> ExecConfig<T> {
effective_gas_price: self.effective_gas_price,
is_dry_run: self.is_dry_run.clone(),
mock_handler: None,
transient_storage: None,
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion substrate/frame/revive/src/tests/sol/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ fn mock_caller_hook_works(caller_type: FixtureType, callee_type: FixtureType) {
mock_call: Default::default(),
mock_delegate_caller: Default::default(),
})),
transient_storage: None,
})
.build_and_unwrap_result();

Expand Down Expand Up @@ -473,6 +474,7 @@ fn mock_call_hook_works(caller_type: FixtureType, callee_type: FixtureType) {
.collect(),
mock_delegate_caller: Default::default(),
})),
transient_storage: None,
})
.build_and_unwrap_result();

Expand Down Expand Up @@ -536,6 +538,7 @@ fn mock_delegatecall_hook_works(caller_type: FixtureType, callee_type: FixtureTy
))
.collect(),
})),
transient_storage: None,
})
.build_and_unwrap_result();

Expand Down Expand Up @@ -722,7 +725,7 @@ fn subcall_effectively_limited_substrate_tx(caller_type: FixtureType, callee_typ
{
// the storage stuff won't work on static or delegate call
if case.is_store_call && !matches!(call_type, Caller::CallType::Call) {
continue
continue;
}

ExtBuilder::default().build().execute_with(|| {
Expand Down
9 changes: 6 additions & 3 deletions substrate/frame/revive/src/transient_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use frame_support::DefaultNoBound;
use sp_runtime::{DispatchError, DispatchResult, Saturating};

/// Meter entry tracks transaction allocations.
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone)]
pub struct MeterEntry {
/// Allocations made in the current transaction.
pub amount: u32,
Expand All @@ -56,7 +56,7 @@ impl MeterEntry {

// The storage meter enforces a limit for each transaction,
// which is calculated as free_storage * (1 - 1/16) for each subsequent frame.
#[derive(DefaultNoBound)]
#[derive(DefaultNoBound, Clone)]
pub struct StorageMeter<T: Config> {
nested_meters: Vec<MeterEntry>,
root_meter: MeterEntry,
Expand Down Expand Up @@ -132,6 +132,7 @@ impl<T: Config> StorageMeter<T> {
}

/// An entry representing a journal change.
#[derive(Clone)]
struct JournalEntry {
key: Vec<u8>,
prev_value: Option<Vec<u8>>,
Expand All @@ -150,6 +151,7 @@ impl JournalEntry {
}

/// A journal containing transient storage modifications.
#[derive(Clone)]
struct Journal(Vec<JournalEntry>);

impl Journal {
Expand All @@ -175,7 +177,7 @@ impl Journal {
}

/// Storage for maintaining the current transaction state.
#[derive(Default)]
#[derive(Default, Clone)]
struct Storage(BTreeMap<Vec<u8>, Vec<u8>>);

impl Storage {
Expand Down Expand Up @@ -203,6 +205,7 @@ impl Storage {
/// recorded in the journal (`write`). When the `commit_transaction` function is called, the marker
/// to the journal index (checkpoint) of when that call was entered is discarded.
/// On `rollback_transaction`, all entries are reverted up to the last checkpoint.
#[derive(Clone)]
pub struct TransientStorage<T: Config> {
// The storage and journal size is limited by the storage meter.
storage: Storage,
Expand Down
Loading