Files
pezkuwi-sdk/bizinikiwi/pezframe/revive/rpc/src/tests.rs
T

788 lines
25 KiB
Rust

// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Test the eth-rpc cli with the kitchensink node.
//! This only includes basic transaction tests, most of the other tests are in the
//! [evm-test-suite](https://github.com/paritytech/evm-test-suite) repository.
use crate::{
cli::{self, CliCommand},
client,
example::TransactionBuilder,
subxt_client::{
self, src_chain::runtime_types::pezpallet_revive::primitives::Code, SrcChainConfig,
},
EthRpcClient,
};
use anyhow::anyhow;
use clap::Parser;
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
use pezpallet_revive::{
create1,
evm::{
Account, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, BlockTag,
HashesOrTransactionInfos, TransactionInfo, H256, U256,
},
};
use std::{sync::Arc, thread};
use subxt::{
backend::rpc::RpcClient,
ext::subxt_rpcs::rpc_params,
tx::{SubmittableTransaction, TxStatus},
OnlineClient,
};
const LOG_TARGET: &str = "eth-rpc-tests";
/// Create a websocket client with a 120s timeout.
async fn ws_client_with_retry(url: &str) -> WsClient {
let timeout = tokio::time::Duration::from_secs(120);
tokio::time::timeout(timeout, async {
loop {
if let Ok(client) = WsClientBuilder::default().build(url).await {
return client;
} else {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
})
.await
.expect("Hit timeout")
}
struct SharedResources {
_node_handle: std::thread::JoinHandle<()>,
_rpc_handle: std::thread::JoinHandle<()>,
}
impl SharedResources {
fn start() -> Self {
// Start pez-revive-dev-node
let _node_handle = thread::spawn(move || {
if let Err(e) = pez_revive_dev_node::command::run_with_args(vec![
"--dev".to_string(),
"--rpc-port=45789".to_string(),
"-lerror,pezsc_rpc_server=info,runtime::revive=debug".to_string(),
]) {
panic!("Node exited with error: {e:?}");
}
});
// Start the rpc server.
let args = CliCommand::parse_from([
"--dev",
"--rpc-port=45788",
"--pez-node-rpc-url=ws://localhost:45789",
"--no-prometheus",
"-linfo,eth-rpc=debug",
]);
let _rpc_handle = thread::spawn(move || {
if let Err(e) = cli::run(args) {
panic!("eth-rpc exited with error: {e:?}");
}
});
Self { _node_handle, _rpc_handle }
}
async fn client() -> WsClient {
ws_client_with_retry("ws://localhost:45788").await
}
fn pez_node_rpc_url() -> &'static str {
"ws://localhost:45789"
}
}
macro_rules! unwrap_call_err(
($err:expr) => {
match $err.downcast_ref::<jsonrpsee::core::client::Error>().unwrap() {
jsonrpsee::core::client::Error::Call(call) => call,
_ => panic!("Expected Call error"),
}
}
);
// Helper functions
/// Prepare multiple EVM transfer transactions with nonce in descending order
async fn prepare_evm_transactions<Client: EthRpcClient + Sync + Send>(
client: &Arc<Client>,
signer: Account,
recipient: pezpallet_revive::evm::Address,
amount: U256,
count: usize,
) -> anyhow::Result<Vec<TransactionBuilder<Client>>> {
let start_nonce =
client.get_transaction_count(signer.address(), BlockTag::Latest.into()).await?;
let mut transactions = Vec::new();
for i in (0..count).rev() {
let nonce = start_nonce.saturating_add(U256::from(i as u64));
let tx_builder = TransactionBuilder::new(client)
.signer(signer.clone())
.nonce(nonce)
.value(amount)
.to(recipient);
transactions.push(tx_builder);
log::trace!(target: LOG_TARGET, "Prepared EVM transaction {}/{count} with nonce: {nonce:?}", i + 1);
}
Ok(transactions)
}
/// Prepare multiple Bizinikiwi transfer transactions with sequential nonces
async fn prepare_bizinikiwi_transactions(
node_client: &OnlineClient<SrcChainConfig>,
signer: &subxt_signer::sr25519::Keypair,
count: usize,
) -> anyhow::Result<Vec<SubmittableTransaction<SrcChainConfig, OnlineClient<SrcChainConfig>>>> {
let mut nonce = node_client.tx().account_nonce(&signer.public_key().into()).await?;
let mut bizinikiwi_txs = Vec::new();
for i in 0..count {
let remark_data = format!("Hello from test {}", i);
let call = subxt::dynamic::tx(
"System",
"remark",
vec![subxt::dynamic::Value::from_bytes(remark_data.as_bytes())],
);
// Note: Using polkadot config from subxt (external crate)
let params = subxt::config::polkadot::PolkadotExtrinsicParamsBuilder::new()
.nonce(nonce)
.build();
let tx = node_client.tx().create_signed(&call, signer, params).await?;
bizinikiwi_txs.push(tx);
log::trace!(target: LOG_TARGET, "Prepared bizinikiwi transaction {i}/{count} with nonce: {nonce}");
nonce += 1 as u64;
}
Ok(bizinikiwi_txs)
}
/// Submit multiple transactions and return them without waiting for receipts
async fn submit_evm_transactions<Client: EthRpcClient + Sync + Send>(
transactions: Vec<TransactionBuilder<Client>>,
) -> anyhow::Result<
Vec<(
H256,
pezpallet_revive::evm::GenericTransaction,
crate::example::SubmittedTransaction<Client>,
)>,
> {
let mut submitted_txs = Vec::new();
for tx_builder in transactions {
let tx = tx_builder.send().await?;
let hash = tx.hash();
let generic_tx = tx.generic_transaction();
submitted_txs.push((hash, generic_tx, tx));
}
Ok(submitted_txs)
}
/// Submit bizinikiwi transactions and return futures for waiting
async fn submit_bizinikiwi_transactions(
bizinikiwi_txs: Vec<SubmittableTransaction<SrcChainConfig, OnlineClient<SrcChainConfig>>>,
) -> Vec<impl std::future::Future<Output = Result<(), anyhow::Error>>> {
let mut futures = Vec::new();
for (i, tx) in bizinikiwi_txs.into_iter().enumerate() {
let fut = async move {
match tx.submit_and_watch().await {
Ok(mut progress) => {
log::trace!(target: LOG_TARGET, "Bizinikiwi tx {i} submitted");
while let Some(status) = progress.next().await {
match status {
Ok(TxStatus::InFinalizedBlock(block)) |
Ok(TxStatus::InBestBlock(block)) => {
log::trace!(target: LOG_TARGET,
"Bizinikiwi tx {i} included in block {:?}",
block.block_hash()
);
return Ok(());
},
Err(e) => return Err(anyhow::anyhow!("Bizinikiwi tx {i} error: {e}")),
Ok(status) => {
log::trace!(target: LOG_TARGET, "Bizinikiwi tx {i} status {:?}", status);
},
}
}
Err(anyhow::anyhow!(
"Failed to get status of submitted bizinikiwi tx {i}, assuming error"
))
},
Err(e) => Err(anyhow::anyhow!("Failed to submit bizinikiwi tx {i}: {e}")),
}
};
futures.push(fut);
}
futures
}
/// Verify all given transaction hashes are in the specified block and accessible via RPC
async fn verify_transactions_in_single_block(
client: &Arc<WsClient>,
block_number: U256,
expected_tx_hashes: &[H256],
) -> anyhow::Result<()> {
// Fetch the block
let block = client
.get_block_by_number(BlockNumberOrTag::U256(block_number), false)
.await?
.ok_or_else(|| anyhow!("Block {block_number} should exist"))?;
let block_tx_hashes = match &block.transactions {
HashesOrTransactionInfos::Hashes(hashes) => hashes.clone(),
HashesOrTransactionInfos::TransactionInfos(infos) =>
infos.iter().map(|info| info.hash).collect(),
};
if let Some(missing_hash) =
expected_tx_hashes.iter().find(|hash| !block_tx_hashes.contains(hash))
{
return Err(anyhow!("Transaction {missing_hash:?} not found in block {block_number}"));
}
Ok(())
}
#[tokio::test]
async fn run_all_eth_rpc_tests() -> anyhow::Result<()> {
// start node and rpc server
let _shared = SharedResources::start();
let client = Arc::new(SharedResources::client().await);
macro_rules! run_tests {
($($test:ident),+ $(,)?) => {
$(
{
let test_name = stringify!($test);
log::debug!(target: LOG_TARGET, "Running test: {}", test_name);
match $test(client.clone()).await {
Ok(()) => log::debug!(target: LOG_TARGET, "Test passed: {}", test_name),
Err(err) => panic!("Test {} failed: {err:?}", test_name),
}
}
)+
};
}
run_tests!(
test_transfer,
test_deploy_and_call,
test_runtime_api_dry_run_addr_works,
test_invalid_transaction,
test_evm_blocks_should_match,
test_evm_blocks_hydrated_should_match,
test_block_hash_for_tag_with_proper_ethereum_block_hash_works,
test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails,
test_block_hash_for_tag_with_block_number_works,
test_block_hash_for_tag_with_block_tags_works,
test_multiple_transactions_in_block,
test_mixed_evm_bizinikiwi_transactions,
test_runtime_pallets_address_upload_code,
);
log::debug!(target: LOG_TARGET, "All tests completed successfully!");
Ok(())
}
async fn test_transfer(client: Arc<WsClient>) -> anyhow::Result<()> {
let ethan = Account::from(subxt_signer::eth::dev::ethan());
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
let value = 1_000_000_000_000_000_000_000u128.into();
let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?;
let receipt = tx.wait_for_receipt().await?;
assert_eq!(
Some(ethan.address()),
receipt.to,
"Receipt should have the correct contract address."
);
let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
assert_eq!(
Some(value),
balance.checked_sub(initial_balance),
"Ethan {:?} {balance:?} should have increased by {value:?} from {initial_balance}.",
ethan.address()
);
Ok(())
}
async fn test_deploy_and_call(client: Arc<WsClient>) -> anyhow::Result<()> {
let account = Account::default();
// Balance transfer
let ethan = Account::from(subxt_signer::eth::dev::ethan());
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
let value = 1_000_000_000_000_000_000_000u128.into();
let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?;
let receipt = tx.wait_for_receipt().await?;
assert_eq!(
Some(ethan.address()),
receipt.to,
"Receipt should have the correct contract address."
);
let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
assert_eq!(
Some(value),
balance.checked_sub(initial_balance),
"Ethan {:?} {balance:?} should have increased by {value:?} from {initial_balance}.",
ethan.address()
);
// Deploy contract
let data = b"hello world".to_vec();
let value = U256::from(5_000_000_000_000u128);
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let input = bytes.into_iter().chain(data.clone()).collect::<Vec<u8>>();
let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?;
let tx = TransactionBuilder::new(&client).value(value).input(input).send().await?;
let receipt = tx.wait_for_receipt().await?;
let contract_address = create1(&account.address(), nonce.try_into().unwrap());
assert_eq!(
Some(contract_address),
receipt.contract_address,
"Contract should be deployed at {contract_address:?}."
);
let nonce_after_deploy =
client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?;
assert_eq!(nonce_after_deploy - nonce, U256::from(1), "Nonce should have increased by 1");
let initial_balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
assert_eq!(
value, initial_balance,
"Contract {contract_address:?} balance should be the same as the value sent ({value})."
);
// Call contract
let tx = TransactionBuilder::new(&client)
.value(value)
.to(contract_address)
.send()
.await?;
let receipt = tx.wait_for_receipt().await?;
assert_eq!(
Some(contract_address),
receipt.to,
"Receipt should have the correct contract address {contract_address:?}."
);
let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
assert_eq!(Some(value), balance.checked_sub(initial_balance), "Contract {contract_address:?} Balance {balance} should have increased from {initial_balance} by {value}.");
// Balance transfer to contract
let initial_balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
let tx = TransactionBuilder::new(&client)
.value(value)
.to(contract_address)
.send()
.await?;
tx.wait_for_receipt().await?;
let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
assert_eq!(
Some(value),
balance.checked_sub(initial_balance),
"Balance {balance} should have increased from {initial_balance} by {value}."
);
Ok(())
}
async fn test_runtime_api_dry_run_addr_works(client: Arc<WsClient>) -> anyhow::Result<()> {
let account = Account::default();
let origin: [u8; 32] = account.bizinikiwi_account().into();
let data = b"hello world".to_vec();
let value = 5_000_000_000_000u128;
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let payload = subxt_client::apis().revive_api().instantiate(
subxt::utils::AccountId32(origin),
value,
None,
None,
Code::Upload(bytes),
data,
None,
);
// runtime_api.at_latest() uses the latest finalized block, query nonce accordingly
let nonce = client
.get_transaction_count(account.address(), BlockTag::Finalized.into())
.await?;
let contract_address = create1(&account.address(), nonce.try_into().unwrap());
let c = OnlineClient::<SrcChainConfig>::from_url("ws://localhost:45789").await?;
let res = c.runtime_api().at_latest().await?.call(payload).await?.result.unwrap();
assert_eq!(res.addr, contract_address);
Ok(())
}
async fn test_invalid_transaction(client: Arc<WsClient>) -> anyhow::Result<()> {
let ethan = Account::from(subxt_signer::eth::dev::ethan());
let err = TransactionBuilder::new(&client)
.value(U256::from(1_000_000_000_000u128))
.to(ethan.address())
.mutate(|tx| tx.chain_id = Some(42u32.into()))
.send()
.await
.unwrap_err();
let call_err = unwrap_call_err!(err.source().unwrap());
assert_eq!(call_err.message(), "Invalid Transaction");
Ok(())
}
async fn get_evm_block_from_storage(
node_client: &OnlineClient<SrcChainConfig>,
pez_node_rpc_client: &RpcClient,
block_number: U256,
) -> anyhow::Result<Block> {
let block_hash: H256 = pez_node_rpc_client
.request("chain_getBlockHash", rpc_params![block_number])
.await
.unwrap();
let query = subxt_client::storage().revive().ethereum_block();
let Some(block) = node_client.storage().at(block_hash).fetch(&query).await.unwrap() else {
return Err(anyhow!("EVM block {block_hash:?} not found"));
};
Ok(block.0)
}
async fn test_evm_blocks_should_match(client: Arc<WsClient>) -> anyhow::Result<()> {
let (node_client, pez_node_rpc_client, _) =
client::connect(SharedResources::pez_node_rpc_url()).await.unwrap();
// Deploy a contract to have some interesting blocks
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let value = U256::from(5_000_000_000_000u128);
let tx = TransactionBuilder::new(&client)
.value(value)
.input(bytes.to_vec())
.send()
.await?;
let receipt = tx.wait_for_receipt().await?;
let block_number = receipt.block_number;
let block_hash = receipt.block_hash;
log::trace!(target: LOG_TARGET, "block_number = {block_number:?}");
log::trace!(target: LOG_TARGET, "tx hash = {:?}", tx.hash());
let evm_block_from_storage =
get_evm_block_from_storage(&node_client, &pez_node_rpc_client, block_number).await?;
// Fetch the block immediately (should come from storage EthereumBlock)
let evm_block_from_rpc_by_number = client
.get_block_by_number(BlockNumberOrTag::U256(block_number.into()), false)
.await?
.expect("Block should exist");
let evm_block_from_rpc_by_hash =
client.get_block_by_hash(block_hash, false).await?.expect("Block should exist");
assert!(
matches!(
evm_block_from_rpc_by_number.transactions,
pezpallet_revive::evm::HashesOrTransactionInfos::Hashes(_)
),
"Block should not have hydrated transactions"
);
// All EVM blocks must match
assert_eq!(evm_block_from_storage, evm_block_from_rpc_by_number, "EVM blocks should match");
assert_eq!(evm_block_from_storage, evm_block_from_rpc_by_hash, "EVM blocks should match");
Ok(())
}
async fn test_evm_blocks_hydrated_should_match(client: Arc<WsClient>) -> anyhow::Result<()> {
// Deploy a contract to have some transactions in the block
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let value = U256::from(5_000_000_000_000u128);
let signer = Account::default();
let signer_copy = Account::default();
let tx = TransactionBuilder::new(&client)
.value(value)
.signer(signer)
.input(bytes.to_vec())
.send()
.await?;
let receipt = tx.wait_for_receipt().await?;
let block_number = receipt.block_number;
let block_hash = receipt.block_hash;
log::trace!(target: LOG_TARGET, "block_number = {block_number:?}");
log::trace!(target: LOG_TARGET, "tx hash = {:?}", tx.hash());
// Fetch the block with hydrated transactions via RPC (by number and by hash)
let evm_block_from_rpc_by_number = client
.get_block_by_number(BlockNumberOrTag::U256(block_number.into()), true)
.await?
.expect("Block should exist");
let evm_block_from_rpc_by_hash =
client.get_block_by_hash(block_hash, true).await?.expect("Block should exist");
// Both blocks should be identical
assert_eq!(
evm_block_from_rpc_by_number, evm_block_from_rpc_by_hash,
"Hydrated EVM blocks should match"
);
// Verify transaction info
let unsigned_tx = tx
.generic_transaction()
.try_into_unsigned()
.expect("Transaction shall be converted");
let signed_tx = signer_copy.sign_transaction(unsigned_tx);
let expected_tx_info = TransactionInfo::new(&receipt, signed_tx);
let tx_info = if let HashesOrTransactionInfos::TransactionInfos(tx_infos) =
evm_block_from_rpc_by_number.transactions
{
tx_infos[0].clone()
} else {
panic!("Expected hydrated transactions");
};
assert_eq!(expected_tx_info, tx_info, "TransationInfos should match");
Ok(())
}
async fn test_block_hash_for_tag_with_proper_ethereum_block_hash_works(
client: Arc<WsClient>,
) -> anyhow::Result<()> {
// Deploy a transaction to create a block with transactions
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let value = U256::from(5_000_000_000_000u128);
let tx = TransactionBuilder::new(&client)
.value(value)
.input(bytes.to_vec())
.send()
.await?;
let receipt = tx.wait_for_receipt().await?;
let ethereum_block_hash = receipt.block_hash;
log::trace!(target: LOG_TARGET, "Testing with Ethereum block hash: {ethereum_block_hash:?}");
let block_by_hash = client
.get_block_by_hash(ethereum_block_hash, false)
.await?
.expect("Block should exist");
let account = Account::default();
let balance = client.get_balance(account.address(), ethereum_block_hash.into()).await?;
assert!(balance >= U256::zero(), "Balance should be retrievable with Ethereum hash");
assert_eq!(block_by_hash.hash, ethereum_block_hash, "Block hash should match");
Ok(())
}
async fn test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails(
client: Arc<WsClient>,
) -> anyhow::Result<()> {
let fake_eth_hash = H256::from([0x42u8; 32]);
log::trace!(target: LOG_TARGET, "Testing with fake Ethereum hash: {fake_eth_hash:?}");
let account = Account::default();
let result = client.get_balance(account.address(), fake_eth_hash.into()).await;
assert!(result.is_err(), "Should fail with non-existent Ethereum hash");
Ok(())
}
async fn test_block_hash_for_tag_with_block_number_works(
client: Arc<WsClient>,
) -> anyhow::Result<()> {
let block_number = client.block_number().await?;
log::trace!(target: LOG_TARGET, "Testing with block number: {block_number}");
let account = Account::default();
let balance = client
.get_balance(account.address(), BlockNumberOrTagOrHash::BlockNumber(block_number))
.await?;
assert!(balance >= U256::zero(), "Balance should be retrievable with block number");
Ok(())
}
async fn test_block_hash_for_tag_with_block_tags_works(
client: Arc<WsClient>,
) -> anyhow::Result<()> {
let account = Account::default();
let tags = vec![
BlockTag::Latest,
BlockTag::Finalized,
BlockTag::Safe,
BlockTag::Earliest,
BlockTag::Pending,
];
for tag in tags {
let balance = client.get_balance(account.address(), tag.clone().into()).await?;
assert!(balance >= U256::zero(), "Balance should be retrievable with tag {tag:?}");
}
Ok(())
}
async fn test_multiple_transactions_in_block(client: Arc<WsClient>) -> anyhow::Result<()> {
let num_transactions = 20;
let alith = Account::default();
let ethan = Account::from(subxt_signer::eth::dev::ethan());
let amount = U256::from(1_000_000_000_000_000_000u128);
// Prepare EVM transfer transactions
let transactions =
prepare_evm_transactions(&client, alith, ethan.address(), amount, num_transactions).await?;
// Submit all transactions
let submitted_txs = submit_evm_transactions(transactions).await?;
let tx_hashes: Vec<H256> = submitted_txs.iter().map(|(hash, _, _)| *hash).collect();
log::trace!(target: LOG_TARGET, "Submitted {} transactions", submitted_txs.len());
// All transactions should be included in the same block since nonces are in descending order
let first_receipt = submitted_txs[0].2.wait_for_receipt().await?;
// Fetch and verify block contains all transactions
verify_transactions_in_single_block(&client, first_receipt.block_number, &tx_hashes).await?;
Ok(())
}
async fn test_mixed_evm_bizinikiwi_transactions(client: Arc<WsClient>) -> anyhow::Result<()> {
let num_evm_txs = 10;
let num_bizinikiwi_txs = 7;
let alith = Account::default();
let ethan = Account::from(subxt_signer::eth::dev::ethan());
let amount = U256::from(500_000_000_000_000_000u128);
// Prepare EVM transactions
log::trace!(target: LOG_TARGET, "Creating {num_evm_txs} EVM transfer transactions");
let evm_transactions =
prepare_evm_transactions(&client, alith, ethan.address(), amount, num_evm_txs).await?;
// Prepare bizinikiwi transactions (simple remarks)
log::trace!(target: LOG_TARGET, "Creating {num_bizinikiwi_txs} bizinikiwi remark transactions");
let alice_signer = subxt_signer::sr25519::dev::alice();
let (node_client, _, _) = client::connect(SharedResources::pez_node_rpc_url()).await.unwrap();
let bizinikiwi_txs =
prepare_bizinikiwi_transactions(&node_client, &alice_signer, num_bizinikiwi_txs).await?;
log::trace!(target: LOG_TARGET, "Submitting {num_evm_txs} EVM and {num_bizinikiwi_txs} bizinikiwi transactions");
// Submit EVM transactions
let evm_submitted = submit_evm_transactions(evm_transactions).await?;
let evm_tx_hashes: Vec<H256> = evm_submitted.iter().map(|(hash, _, _)| *hash).collect();
// Submit bizinikiwi transactions
let bizinikiwi_futures = submit_bizinikiwi_transactions(bizinikiwi_txs).await;
// Wait for first EVM receipt and all bizinikiwi transactions in parallel
let (evm_first_receipt_result, _bizinikiwi_results) = tokio::join!(
async { evm_submitted[0].2.wait_for_receipt().await },
futures::future::join_all(bizinikiwi_futures)
);
// Handle the EVM receipt result
let evm_first_receipt = evm_first_receipt_result?;
// Fetch and verify block contains all transactions
verify_transactions_in_single_block(&client, evm_first_receipt.block_number, &evm_tx_hashes)
.await?;
Ok(())
}
async fn test_runtime_pallets_address_upload_code(client: Arc<WsClient>) -> anyhow::Result<()> {
let (node_client, pez_node_rpc_client, _) =
client::connect(SharedResources::pez_node_rpc_url()).await?;
let (bytecode, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
let signer = Account::default();
// Helper function to get bizinikiwi block hash from EVM block number
let get_bizinikiwi_block_hash = |block_number: U256| {
let rpc_client = pez_node_rpc_client.clone();
async move {
rpc_client
.request::<pezsp_core::H256>("chain_getBlockHash", rpc_params![block_number])
.await
}
};
// Step 1: Encode the Bizinikiwi upload_code call
let upload_call = subxt::dynamic::tx(
"Revive",
"upload_code",
vec![
subxt::dynamic::Value::from_bytes(&bytecode),
subxt::dynamic::Value::u128(u128::max_value()), // storage_deposit_limit
],
);
let encoded_call = node_client.tx().call_data(&upload_call)?;
// Step 2: Send the encoded call to RUNTIME_PALLETS_ADDR
let tx = TransactionBuilder::new(&client)
.signer(signer.clone())
.to(pezpallet_revive::RUNTIME_PALLETS_ADDR)
.input(encoded_call.clone())
.send()
.await?;
// Step 3: Wait for receipt
let receipt = tx.wait_for_receipt().await?;
// Step 4: Verify transaction was successful
assert_eq!(
receipt.status.unwrap_or(U256::zero()),
U256::one(),
"Transaction should be successful"
);
// Step 5: Verify the code was actually uploaded
let code_hash = H256(pezsp_io::hashing::keccak_256(&bytecode));
let query = subxt_client::storage().revive().pristine_code(code_hash);
let block_hash: pezsp_core::H256 = get_bizinikiwi_block_hash(receipt.block_number).await?;
let stored_code = node_client.storage().at(block_hash).fetch(&query).await?;
assert!(stored_code.is_some(), "Code with hash {code_hash:?} should exist in storage");
assert_eq!(stored_code.unwrap(), bytecode, "Stored code should match the uploaded bytecode");
Ok(())
}