use crate::{
Blockchain, ExecutorConfig, ForkRpcClient, RemoteStorageLayer, RuntimeExecutor, StorageCache,
TxPool,
rpc_server::{ForkRpcServer, RpcServerConfig},
};
use pop_common::test_env::TestNode;
use std::sync::Arc;
use subxt::{Metadata, config::substrate::H256};
use url::Url;
pub mod constants {
pub const SYSTEM_NUMBER_KEY: &str =
"26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac";
pub const SYSTEM_PARENT_HASH_KEY: &str =
"26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96";
pub const SYSTEM_PALLET_PREFIX: &str = "26aa394eea5630e07c48ae0c9558cef7";
pub const TRANSFER_AMOUNT: u128 = 100_000_000_000_000;
}
pub mod accounts {
pub use crate::dev::{ALICE, BOB};
}
pub mod helpers {
use super::accounts::ALICE;
use scale::{Compact, Encode};
pub use crate::dev::account_storage_key;
pub fn decode_free_balance(data: &[u8]) -> u128 {
const ACCOUNT_DATA_OFFSET: usize = 16;
u128::from_le_bytes(
data[ACCOUNT_DATA_OFFSET..ACCOUNT_DATA_OFFSET + 16]
.try_into()
.expect("need 16 bytes for u128"),
)
}
pub fn build_mock_signed_extrinsic_v4(call_data: &[u8]) -> Vec<u8> {
let mut inner = Vec::new();
inner.push(0x84); inner.push(0x00); inner.extend(ALICE);
inner.extend([0u8; 64]); inner.push(0x00); inner.extend(Compact(0u64).encode()); inner.extend(Compact(0u128).encode()); inner.push(0x00); inner.extend(call_data);
let mut extrinsic = Compact(inner.len() as u32).encode();
extrinsic.extend(inner);
extrinsic
}
}
pub struct TestContext {
#[allow(dead_code)]
pub node: TestNode,
pub endpoint: Url,
pub rpc: Option<ForkRpcClient>,
pub cache: Option<StorageCache>,
pub block_hash: Option<H256>,
pub block_number: Option<u32>,
pub metadata: Option<Metadata>,
pub remote: Option<RemoteStorageLayer>,
pub blockchain: Option<Arc<Blockchain>>,
pub server: Option<ForkRpcServer>,
pub txpool: Option<Arc<TxPool>>,
pub executor: Option<RuntimeExecutor>,
}
impl TestContext {
pub async fn minimal() -> Self {
TestContextBuilder::new().build().await
}
pub async fn for_rpc_client() -> Self {
TestContextBuilder::new().with_rpc().build().await
}
pub async fn for_storage() -> Self {
TestContextBuilder::new()
.with_rpc()
.with_cache()
.with_block_info()
.build()
.await
}
pub async fn for_remote() -> Self {
TestContextBuilder::new().with_remote().with_block_info().build().await
}
pub async fn for_local() -> Self {
TestContextBuilder::new()
.with_remote()
.with_block_info()
.with_metadata()
.build()
.await
}
pub async fn for_blockchain() -> Self {
TestContextBuilder::new().with_blockchain().build().await
}
pub async fn for_blockchain_with_config(config: ExecutorConfig) -> Self {
TestContextBuilder::new()
.with_blockchain()
.executor_config(config)
.build()
.await
}
pub async fn for_rpc_server() -> Self {
TestContextBuilder::new().with_server().build().await
}
pub async fn for_rpc_server_with_config(config: ExecutorConfig) -> Self {
TestContextBuilder::new().with_server().executor_config(config).build().await
}
pub async fn for_executor() -> Self {
TestContextBuilder::new().with_executor().build().await
}
pub async fn for_executor_with_config(config: ExecutorConfig) -> Self {
TestContextBuilder::new().with_executor().executor_config(config).build().await
}
pub fn rpc(&self) -> &ForkRpcClient {
self.rpc.as_ref().expect("rpc not initialized - use with_rpc()")
}
pub fn cache(&self) -> &StorageCache {
self.cache.as_ref().expect("cache not initialized - use with_cache()")
}
pub fn block_hash(&self) -> H256 {
self.block_hash.expect("block_hash not initialized - use with_block_info()")
}
pub fn block_number(&self) -> u32 {
self.block_number.expect("block_number not initialized - use with_block_info()")
}
pub fn metadata(&self) -> &Metadata {
self.metadata.as_ref().expect("metadata not initialized - use with_metadata()")
}
pub fn remote(&self) -> &RemoteStorageLayer {
self.remote.as_ref().expect("remote not initialized - use with_remote()")
}
pub fn blockchain(&self) -> &Arc<Blockchain> {
self.blockchain
.as_ref()
.expect("blockchain not initialized - use with_blockchain()")
}
pub fn server(&self) -> &ForkRpcServer {
self.server.as_ref().expect("server not initialized - use with_server()")
}
pub fn txpool(&self) -> &Arc<TxPool> {
self.txpool.as_ref().expect("txpool not initialized - use with_server()")
}
pub fn executor(&self) -> &RuntimeExecutor {
self.executor.as_ref().expect("executor not initialized - use with_executor()")
}
pub fn ws_url(&self) -> String {
self.server().ws_url()
}
}
#[derive(Default)]
pub struct TestContextBuilder {
executor_config: Option<ExecutorConfig>,
with_rpc: bool,
with_cache: bool,
with_block_info: bool,
with_metadata: bool,
with_remote: bool,
with_blockchain: bool,
with_server: bool,
with_executor: bool,
}
impl TestContextBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_rpc(mut self) -> Self {
self.with_rpc = true;
self
}
pub fn with_cache(mut self) -> Self {
self.with_cache = true;
self
}
pub fn with_block_info(mut self) -> Self {
self.with_block_info = true;
self
}
pub fn with_metadata(mut self) -> Self {
self.with_metadata = true;
self
}
pub fn with_remote(mut self) -> Self {
self.with_remote = true;
self.with_rpc = true;
self.with_cache = true;
self
}
pub fn with_blockchain(mut self) -> Self {
self.with_blockchain = true;
self
}
pub fn with_server(mut self) -> Self {
self.with_server = true;
self.with_blockchain = true;
self
}
pub fn with_executor(mut self) -> Self {
self.with_executor = true;
self.with_rpc = true;
self.with_cache = true;
self.with_block_info = true;
self
}
pub fn executor_config(mut self, config: ExecutorConfig) -> Self {
self.executor_config = Some(config);
self
}
pub async fn build(self) -> TestContext {
let node = TestNode::spawn().await.expect("Failed to spawn test node");
let endpoint: Url = node.ws_url().parse().expect("Invalid WebSocket URL");
let rpc = if self.with_rpc || self.with_block_info || self.with_metadata {
Some(ForkRpcClient::connect(&endpoint).await.expect("Failed to connect RPC client"))
} else {
None
};
let cache = if self.with_cache {
Some(StorageCache::in_memory().await.expect("Failed to create cache"))
} else {
None
};
let (block_hash, block_number) = if self.with_block_info {
let rpc_ref = rpc.as_ref().expect("RPC required for block info");
let hash = rpc_ref.finalized_head().await.expect("Failed to get finalized head");
let header = rpc_ref.header(hash).await.expect("Failed to get header");
(Some(hash), Some(header.number))
} else {
(None, None)
};
let metadata = if self.with_metadata {
let rpc_ref = rpc.as_ref().expect("RPC required for metadata");
let hash = block_hash.unwrap_or_else(|| {
panic!("block_hash required for metadata - use with_block_info()")
});
Some(rpc_ref.metadata(hash).await.expect("Failed to get metadata"))
} else {
None
};
let remote = if self.with_remote {
let rpc_clone = rpc.clone().expect("RPC required for remote");
let cache_clone = cache.clone().expect("Cache required for remote");
Some(RemoteStorageLayer::new(rpc_clone, cache_clone))
} else {
None
};
let blockchain = if self.with_blockchain {
let config = self.executor_config.clone().unwrap_or_default();
Some(
Blockchain::fork_with_config(&endpoint, None, None, config)
.await
.expect("Failed to fork blockchain"),
)
} else {
None
};
let txpool = if self.with_server { Some(Arc::new(TxPool::new())) } else { None };
let server = if self.with_server {
let blockchain_ref = blockchain.clone().expect("Blockchain required for server");
let txpool_ref = txpool.clone().expect("TxPool required for server");
Some(
ForkRpcServer::start(blockchain_ref, txpool_ref, RpcServerConfig::default())
.await
.expect("Failed to start RPC server"),
)
} else {
None
};
let executor = if self.with_executor {
let rpc_ref = rpc.as_ref().expect("RPC required for executor");
let hash = block_hash.expect("block_hash required for executor");
let code_key = sp_core::storage::well_known_keys::CODE;
let runtime_code = rpc_ref
.storage(code_key, hash)
.await
.expect("Failed to get runtime code")
.expect("Runtime code not found");
let config = self.executor_config.unwrap_or_default();
Some(
RuntimeExecutor::with_config(runtime_code, None, config)
.expect("Failed to create executor"),
)
} else {
None
};
TestContext {
node,
endpoint,
rpc,
cache,
block_hash,
block_number,
metadata,
remote,
blockchain,
server,
txpool,
executor,
}
}
}