Skip to content

Commit b98ae85

Browse files
committed
upd
1 parent 5f69b6f commit b98ae85

File tree

10 files changed

+471
-352
lines changed

10 files changed

+471
-352
lines changed

Cargo.lock

Lines changed: 352 additions & 335 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ revive-strategy = { path = "crates/revive-strategy" }
217217
revive-utils = { path = "crates/revive-utils" }
218218

219219
# polkadot-sdk
220-
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", features = [
220+
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage", features = [
221221
"experimental",
222222
"runtime",
223223
"polkadot-runtime-common",

crates/anvil-polkadot/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ path = "bin/main.rs"
2121
# foundry internal
2222
codec = { version = "3.7.5", default-features = true, package = "parity-scale-codec" }
2323
substrate-runtime = { path = "substrate-runtime" }
24-
pallet-revive-eth-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" }
24+
pallet-revive-eth-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage"}
2525
secp256k1 = { version = "0.28.0", default-features = false }
2626
libsecp256k1 = { version = "0.7.0", default-features = false }
27-
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false }
28-
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false, features = [
27+
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage", default-features = false }
28+
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage", default-features = false, features = [
2929
"sc-allocator",
3030
"sc-basic-authorship",
3131
"sc-block-builder",

crates/anvil-polkadot/substrate-runtime/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license.workspace = true
1212
[dependencies]
1313
array-bytes = { version = "6.2.2", default-features = false }
1414
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
15-
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false, features = [
15+
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage", default-features = false, features = [
1616
"pallet-aura",
1717
"pallet-balances",
1818
"pallet-revive",
@@ -29,7 +29,7 @@ scale-info = { version = "2.11.6", default-features = false }
2929
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
3030

3131
[build-dependencies]
32-
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false, optional = true, features = ["substrate-wasm-builder"] }
32+
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "pkhry/external_transient_storage", default-features = false, optional = true, features = ["substrate-wasm-builder"] }
3333

3434
[features]
3535
default = ["std"]

crates/forge/tests/it/revive/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ pub mod cheat_store;
1212
pub mod cheats_individual;
1313
pub mod migration;
1414
pub mod record_accesses;
15+
pub mod transient_storage;
1516
pub mod tx_gas_price;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use crate::{config::*, test_helpers::TEST_DATA_REVIVE};
2+
use foundry_test_utils::Filter;
3+
use revive_strategy::ReviveRuntimeMode;
4+
use revm::primitives::hardfork::SpecId;
5+
use rstest::rstest;
6+
7+
#[rstest]
8+
#[case::pvm(ReviveRuntimeMode::Pvm)]
9+
#[case::evm(ReviveRuntimeMode::Evm)]
10+
#[tokio::test(flavor = "multi_thread")]
11+
async fn test_transient_storage(#[case] runtime_mode: ReviveRuntimeMode) {
12+
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode);
13+
let filter = Filter::new("testTransientStoragePersistence", "TransientStorage", ".*/revive/.*");
14+
15+
TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
16+
}

crates/revive-strategy/src/cheatcodes/mod.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
10821082
let caller_h160 = H160::from_slice(input.caller().as_slice());
10831083
let eth_deals = &state.eth_deals;
10841084

1085-
let res = ctx.externalities.execute_with(|| {
1085+
let res = ctx.externalities.execute_with_transient_storage(|transient_storage| {
10861086
tracer.watch_address(&caller_h160);
10871087

10881088
tracer.trace(|| {
@@ -1135,9 +1135,10 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
11351135
effective_gas_price: Some(gas_price_pvm),
11361136
mock_handler: Some(Box::new(mock_handler.clone())),
11371137
is_dry_run: None,
1138+
transient_storage: Some(transient_storage),
11381139
};
11391140

1140-
Pallet::<Runtime>::bare_instantiate(
1141+
let result = Pallet::<Runtime>::bare_instantiate(
11411142
origin,
11421143
evm_value,
11431144
pallet_revive::TransactionLimits::WeightAndDeposit {
@@ -1147,8 +1148,9 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
11471148
code,
11481149
data,
11491150
salt,
1150-
exec_config,
1151-
)
1151+
&exec_config,
1152+
);
1153+
(result, exec_config.transient_storage.expect("can't happen"))
11521154
})
11531155
});
11541156
let mut gas = Gas::new(input.gas_limit());
@@ -1292,7 +1294,7 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
12921294

12931295
let mut tracer = Tracer::new(state.expected_calls.clone(), state.expected_creates.clone());
12941296
let eth_deals = &state.eth_deals;
1295-
let res = ctx.externalities.execute_with(|| {
1297+
let res = ctx.externalities.execute_with_transient_storage(|transient_storage| {
12961298
// Watch the caller's address so its nonce changes get tracked in prestate trace
12971299
tracer.watch_address(&caller_h160);
12981300

@@ -1310,13 +1312,14 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
13101312
effective_gas_price: Some(gas_price_pvm),
13111313
mock_handler: Some(Box::new(mock_handler.clone())),
13121314
is_dry_run: None,
1315+
transient_storage: Some(transient_storage),
13131316
};
13141317
if should_bump_nonce {
13151318
System::inc_account_nonce(
13161319
AccountId32Mapper::<Runtime>::to_fallback_account_id(&caller_h160),
13171320
);
13181321
}
1319-
Pallet::<Runtime>::bare_call(
1322+
let result = Pallet::<Runtime>::bare_call(
13201323
origin,
13211324
target,
13221325
evm_value,
@@ -1325,8 +1328,9 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
13251328
deposit_limit: if call.is_static { 0 } else { 100_000_000_000_000 },
13261329
},
13271330
call.input.bytes(ecx).to_vec(),
1328-
exec_config,
1329-
)
1331+
&exec_config,
1332+
);
1333+
(result, exec_config.transient_storage.expect("can't happen"))
13301334
})
13311335
});
13321336
mock_handler.update_state_mocks(state);

crates/revive-strategy/src/executor/runner.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,13 @@ impl ExecutorStrategyExt for ReviveExecutorStrategyRunner {
158158
let ctx = get_context_ref(ctx);
159159
let mut externalities = ctx.externalties.0.lock().unwrap();
160160
externalities.externalities.ext().storage_start_transaction();
161+
externalities.transient_storage.start_transaction();
161162
}
162163

163164
fn rollback_transaction(&self, ctx: &dyn ExecutorStrategyContext) {
164165
let ctx = get_context_ref(ctx);
165166
let mut state = ctx.externalties.0.lock().unwrap();
166167
let _ = state.externalities.ext().storage_rollback_transaction();
168+
state.transient_storage.rollback_transaction();
167169
}
168170
}

crates/revive-strategy/src/state.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use polkadot_sdk::{
77
},
88
pallet_revive::{
99
self, AccountId32Mapper, AccountInfo, AddressMapper, BytecodeType, ContractInfo,
10-
ExecConfig, Executable, HoldReason, Pallet, ResourceMeter,
10+
ExecConfig, Executable, HoldReason, Pallet, ResourceMeter, TRANSIENT_STORAGE_LIMIT,
11+
TransientStorage,
1112
},
1213
sp_core::{self, H160, H256},
1314
sp_externalities::Externalities,
@@ -17,13 +18,15 @@ use polkadot_sdk::{
1718
};
1819
use revive_env::{Balances, BlockAuthor, ExtBuilder, NativeToEthRatio, Runtime, System, Timestamp};
1920
use std::{
21+
cell::RefCell,
2022
fmt::Debug,
2123
sync::{Arc, Mutex},
2224
};
2325

2426
pub(crate) struct Inner {
2527
pub externalities: TestExternalities,
2628
pub depth: usize,
29+
pub transient_storage: TransientStorage<Runtime>,
2730
}
2831

2932
#[derive(Default)]
@@ -39,6 +42,7 @@ impl Default for Inner {
3942
)])
4043
.build(),
4144
depth: 0,
45+
transient_storage: TransientStorage::new(TRANSIENT_STORAGE_LIMIT),
4246
}
4347
}
4448
}
@@ -52,8 +56,10 @@ impl Debug for TestEnv {
5256
impl Clone for TestEnv {
5357
fn clone(&self) -> Self {
5458
let mut inner: Inner = Default::default();
55-
inner.externalities.backend = self.0.lock().unwrap().externalities.as_backend();
56-
inner.depth = self.0.lock().unwrap().depth;
59+
let mut data = self.0.lock().unwrap();
60+
inner.externalities.backend = data.externalities.as_backend();
61+
inner.transient_storage = data.transient_storage.clone();
62+
inner.depth = data.depth;
5763
Self(Arc::new(Mutex::new(inner)))
5864
}
5965
}
@@ -67,22 +73,43 @@ impl TestEnv {
6773
let mut state = self.0.lock().unwrap();
6874
state.depth += 1;
6975
state.externalities.ext().storage_start_transaction();
76+
state.transient_storage.start_transaction();
7077
}
7178

7279
pub fn revert(&mut self, depth: usize) {
7380
let mut state = self.0.lock().unwrap();
7481
while state.depth > depth + 1 {
7582
state.externalities.ext().storage_rollback_transaction().unwrap();
83+
state.transient_storage.rollback_transaction();
7684
state.depth -= 1;
7785
}
7886
state.externalities.ext().storage_rollback_transaction().unwrap();
87+
state.transient_storage.rollback_transaction();
7988
state.externalities.ext().storage_start_transaction();
89+
state.transient_storage.start_transaction();
8090
}
8191

8292
pub fn execute_with<R, F: FnOnce() -> R>(&mut self, f: F) -> R {
8393
self.0.lock().unwrap().externalities.execute_with(f)
8494
}
8595

96+
pub fn execute_with_transient_storage<
97+
R,
98+
F: FnOnce(RefCell<TransientStorage<Runtime>>) -> (R, RefCell<TransientStorage<Runtime>>),
99+
>(
100+
&mut self,
101+
f: F,
102+
) -> R {
103+
let mut data = self.0.lock().unwrap();
104+
let mut temp = TransientStorage::new(0);
105+
std::mem::swap(&mut data.transient_storage, &mut temp);
106+
let transient_storage = RefCell::new(temp);
107+
let to_use = transient_storage.clone();
108+
let (result, transient_storage) = data.externalities.execute_with(|| f(to_use));
109+
data.transient_storage = transient_storage.into_inner();
110+
result
111+
}
112+
86113
pub fn get_nonce(&mut self, account: Address) -> u32 {
87114
self.0.lock().unwrap().externalities.execute_with(|| {
88115
System::account_nonce(AccountId32Mapper::<Runtime>::to_fallback_account_id(
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
import "../../default/logs/console.sol";
7+
8+
/**
9+
* @title Minimal reproducer for foundry-polkadot transient storage bug
10+
* @dev Demonstrates that transient storage is incorrectly cleared between external calls
11+
*/
12+
contract Counter {
13+
function increment() external {
14+
assembly {
15+
let value := tload(0)
16+
tstore(0, add(value, 1))
17+
}
18+
}
19+
20+
function get() external view returns (uint256 value) {
21+
assembly {
22+
value := tload(0)
23+
}
24+
}
25+
}
26+
27+
contract TransientStorage is DSTest {
28+
Vm constant vm = Vm(HEVM_ADDRESS);
29+
Counter counter;
30+
31+
function setUp() public {
32+
counter = new Counter();
33+
}
34+
35+
/**
36+
* @dev This test FAILS in foundry-polkadot
37+
* Expected: counter.get() returns 1
38+
* Actual: counter.get() returns 0
39+
*
40+
* The bug: transient storage is cleared between the two external calls
41+
* According to EIP-1153, transient storage should persist for the entire transaction
42+
*/
43+
function testTransientStoragePersistence() public {
44+
// Store value 1 in transient storage
45+
counter.increment();
46+
47+
// This should return 1, but returns 0 in foundry-polkadot
48+
uint256 value = counter.get();
49+
50+
assertEq(value, 1, "Transient storage should persist across external calls");
51+
}
52+
}

0 commit comments

Comments
 (0)