Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(wallet-integration): unit tests for pop up contract #374

Merged
merged 27 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9f7f430
chore: get_payload_works
al3mart Dec 11, 2024
1485dfa
test(wallet-integration): up contract get payload works
al3mart Dec 12, 2024
55addf5
get paylaod from server works
al3mart Dec 12, 2024
e6a5d6a
test(wallet-integration): retrieve upload call data works
al3mart Dec 12, 2024
3bd4c7b
test(wallet-integration): retrieve instantiate call data works
al3mart Dec 12, 2024
c03f658
style: better comments
al3mart Dec 12, 2024
3e8266d
test: try higher wait times in CI
al3mart Dec 12, 2024
a391771
test(wallet-integration): bump sleep time
al3mart Dec 12, 2024
193582a
test(wallet-integration): even more sleep time
al3mart Dec 12, 2024
0075e94
test(wallet-integration): maybe a port problem ?
al3mart Dec 12, 2024
50bd1c8
revert 0075e94
al3mart Dec 12, 2024
b3d6aa3
test(wallet-integration): better unit tests
al3mart Dec 13, 2024
ebaf925
test(wallet-integration): wait for wallet signature
al3mart Dec 13, 2024
4f7798e
test(wallet-integration): assert on received payload
al3mart Dec 13, 2024
3460818
test(wallet-integration): use tokio spawn
al3mart Dec 13, 2024
6f2dd8c
test(wallet-integration): add some waiting time
al3mart Dec 13, 2024
0784af7
test(wallet-integration): use cargo run
al3mart Dec 13, 2024
9cb21a0
style: nightly fmt
al3mart Dec 13, 2024
4c4d3b2
test(wallet-integration): 500s sleep time
al3mart Dec 13, 2024
13cc493
test(wallet-integration): ignore some tests
al3mart Dec 13, 2024
1edf8dc
test(wallet-integration): get instantiate call data
al3mart Dec 13, 2024
76b8aec
test(wallet-integration): integration tests improvements
al3mart Dec 13, 2024
50818a4
test(wallet-integration): bump sleep time
al3mart Dec 13, 2024
59509cb
test(wallet-integration): merge integration tests
al3mart Dec 13, 2024
f9851d1
test(wallet-integration): remove sign_call_data test
al3mart Dec 15, 2024
80f4f7b
test(wallet-integration): use random free port
al3mart Dec 15, 2024
86ab4b8
test(wallet-integration): define gas_limit & proof_size
al3mart Dec 15, 2024
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
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ walkdir = "2.5"
indexmap = "2.2"
toml_edit = { version = "0.22", features = ["serde"] }
symlink = "0.1"
scale-info = { version = "2.11.3", default-features = false, features = ["derive"] }
scale-value = { version = "0.16.2", default-features = false, features = ["from-string", "parser-ss58"] }
scale-info = { version = "2.11.3", default-features = false, features = [
"derive",
] }
scale-value = { version = "0.16.2", default-features = false, features = [
"from-string",
"parser-ss58",
] }
serde_json = { version = "1.0", features = ["preserve_order"] }
serde = { version = "1.0", features = ["derive"] }
zombienet-sdk = "0.2.14"
Expand Down
2 changes: 2 additions & 0 deletions crates/pop-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ tower-http = { workspace = true, features = ["fs"] }

[dev-dependencies]
assert_cmd.workspace = true
contract-extrinsics.workspace = true
predicates.workspace = true
subxt.workspace = true
subxt-signer.workspace = true
sp-weights.workspace = true

[features]
default = ["contract", "parachain", "telemetry"]
Expand Down
136 changes: 131 additions & 5 deletions crates/pop-cli/src/commands/up/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,16 @@ fn display_contract_info(spinner: &ProgressBar, address: String, code_hash: Opti
#[cfg(test)]
mod tests {
use super::*;
use pop_common::{find_free_port, set_executable_permission};
use pop_contracts::{contracts_node_generator, mock_build_process, new_environment};
use std::{
env,
process::{Child, Command},
time::Duration,
};
use subxt::{tx::Payload, SubstrateConfig};
use tempfile::TempDir;
use tokio::time::sleep;
use url::Url;

fn default_up_contract_command() -> UpContractCommand {
Expand All @@ -417,6 +427,28 @@ mod tests {
}
}

async fn start_test_environment() -> anyhow::Result<(Child, u16, TempDir)> {
let random_port = find_free_port();
let temp_dir = new_environment("testing")?;
let current_dir = env::current_dir().expect("Failed to get current directory");
mock_build_process(
temp_dir.path().join("testing"),
current_dir.join("../pop-contracts/tests/files/testing.contract"),
current_dir.join("../pop-contracts/tests/files/testing.json"),
)?;
let cache = temp_dir.path().join("");
let binary = contracts_node_generator(cache.clone(), None).await?;
binary.source(false, &(), true).await?;
set_executable_permission(binary.path())?;
let process = run_contracts_node(binary.path(), None, random_port).await?;
Ok((process, random_port, temp_dir))
}

fn stop_test_environment(id: &str) -> anyhow::Result<()> {
Command::new("kill").args(["-s", "TERM", id]).spawn()?.wait()?;
Ok(())
}

#[test]
fn conversion_up_contract_command_to_up_opts_works() -> anyhow::Result<()> {
let command = default_up_contract_command();
Expand All @@ -438,15 +470,109 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn get_upload_call_data_works() -> anyhow::Result<()> {
todo!()
let (contracts_node_process, port, temp_dir) = start_test_environment().await?;
let localhost_url = format!("ws://127.0.0.1:{}", port);
sleep(Duration::from_secs(20)).await;

let up_contract_opts = UpContractCommand {
path: Some(temp_dir.path().join("testing")),
constructor: "new".to_string(),
args: vec![],
value: "0".to_string(),
gas_limit: None,
proof_size: None,
salt: None,
url: Url::parse(&localhost_url).expect("given url is valid"),
suri: "//Alice".to_string(),
dry_run: false,
upload_only: true,
skip_confirm: true,
use_wallet: true,
};

let rpc_client = subxt::backend::rpc::RpcClient::from_url(&up_contract_opts.url).await?;
let client = subxt::OnlineClient::<SubstrateConfig>::from_rpc_client(rpc_client).await?;

// Retrieve call data based on the above command options.
let (retrieved_call_data, _) = match up_contract_opts.get_contract_data().await {
Ok(data) => data,
Err(e) => {
error(format!("An error occurred getting the call data: {e}"))?;
return Err(e);
},
};
// We have retrieved some payload.
assert!(!retrieved_call_data.is_empty());

// Craft encoded call data for an upload code call.
let contract_code = get_contract_code(up_contract_opts.path.as_ref()).await?;
let storage_deposit_limit: Option<u128> = None;
let upload_code = contract_extrinsics::extrinsic_calls::UploadCode::new(
contract_code,
storage_deposit_limit,
contract_extrinsics::upload::Determinism::Enforced,
);
let expected_call_data = upload_code.build();
let mut encoded_expected_call_data = Vec::<u8>::new();
expected_call_data
.encode_call_data_to(&client.metadata(), &mut encoded_expected_call_data)?;

// Retrieved call data and calculated match.
assert_eq!(retrieved_call_data, encoded_expected_call_data);

// Stop running contracts-node
stop_test_environment(&contracts_node_process.id().to_string())?;
Ok(())
}

#[tokio::test]
async fn get_instantiate_call_data_works() -> anyhow::Result<()> {
todo!()
}
let (contracts_node_process, port, temp_dir) = start_test_environment().await?;
let localhost_url = format!("ws://127.0.0.1:{}", port);
sleep(Duration::from_secs(20)).await;

let up_contract_opts = UpContractCommand {
path: Some(temp_dir.path().join("testing")),
constructor: "new".to_string(),
args: vec!["false".to_string()],
value: "0".to_string(),
gas_limit: None,
proof_size: None,
Copy link
Contributor

Choose a reason for hiding this comment

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

would be nice if these were some value to ensure pop-cli isn't just taking the default None.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 86ab4b8

salt: None,
url: Url::parse(&localhost_url).expect("given url is valid"),
suri: "//Alice".to_string(),
dry_run: false,
upload_only: false,
skip_confirm: true,
use_wallet: true,
};

async fn wait_for_signature_works() -> anyhow::Result<()> {
todo!()
let rpc_client = subxt::backend::rpc::RpcClient::from_url(&up_contract_opts.url).await?;
let client = subxt::OnlineClient::<SubstrateConfig>::from_rpc_client(rpc_client).await?;

// Retrieve call data based on the above command options.
let (retrieved_call_data, _) = match up_contract_opts.get_contract_data().await {
Ok(data) => data,
Err(e) => {
error(format!("An error occurred getting the call data: {e}"))?;
return Err(e);
},
};
// We have retrieved some payload.
assert!(!retrieved_call_data.is_empty());

// Craft instantiate call data.
let weight = Weight::from_parts(0, 0);
let expected_call_data =
get_instantiate_payload(set_up_deployment(up_contract_opts.into()).await?, weight)
.await?;
// Retrieved call data matches the one crafted above.
assert_eq!(retrieved_call_data, expected_call_data);

// Stop running contracts-node
stop_test_environment(&contracts_node_process.id().to_string())?;
Ok(())
}
}
1 change: 1 addition & 0 deletions crates/pop-cli/src/common/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ mod tests {
// This is a helper test for an actual running pop CLI.
// It can serve as the "frontend" to query the payload, sign it
// and submit back to the CLI.
#[ignore]
#[tokio::test]
async fn sign_call_data() -> anyhow::Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

can be removed if its purpose is properly represented elsewhere in the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Even more now that we have the front end part too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in f9851d1

use subxt::{config::DefaultExtrinsicParamsBuilder as Params, tx::Payload};
Expand Down
127 changes: 126 additions & 1 deletion crates/pop-cli/tests/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,45 @@ use pop_contracts::{
contracts_node_generator, dry_run_gas_estimate_instantiate, instantiate_smart_contract,
run_contracts_node, set_up_deployment, Contract, UpOpts,
};
use std::{path::Path, process::Command as Cmd};
use serde::{Deserialize, Serialize};
use std::{path::Path, process::Command as Cmd, time::Duration};
use strum::VariantArray;
use subxt::{config::DefaultExtrinsicParamsBuilder as Params, tx::Payload, utils::to_hex};
use subxt_signer::sr25519::dev;
use tokio::time::sleep;
use url::Url;

// This struct implements the [`Payload`] trait and is used to submit
// pre-encoded SCALE call data directly, without the dynamic construction of transactions.
struct CallData(Vec<u8>);
impl Payload for CallData {
Copy link
Contributor

Choose a reason for hiding this comment

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

leaving a note: I copied this from the pop call parachain PR (IIRC). Once everything gets merged in we should hopefully be able to just import this.

fn encode_call_data_to(
&self,
_: &subxt::Metadata,
out: &mut Vec<u8>,
) -> Result<(), subxt::ext::subxt_core::Error> {
out.extend_from_slice(&self.0);
Ok(())
}
}

// TransactionData has been copied from wallet_integration.rs
/// Transaction payload to be sent to frontend for signing.
#[derive(Serialize, Debug)]
#[cfg_attr(test, derive(Deserialize, Clone))]
pub struct TransactionData {
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason this wasn't just imported?

chain_rpc: String,
call_data: Vec<u8>,
}
impl TransactionData {
pub fn new(chain_rpc: String, call_data: Vec<u8>) -> Self {
Self { chain_rpc, call_data }
}
pub fn call_data(&self) -> Vec<u8> {
self.call_data.clone()
}
}

/// Test the contract lifecycle: new, build, up, call
#[tokio::test]
async fn contract_lifecycle() -> Result<()> {
Expand Down Expand Up @@ -135,6 +170,96 @@ async fn contract_lifecycle() -> Result<()> {
Ok(())
}

#[tokio::test]
async fn wait_for_wallet_signature() -> Result<()> {
const DEFAULT_ENDPOINT: &str = "ws://127.0.0.1:9966";
const DEFAULT_PORT: u16 = 9966;
const WALLET_INT_URI: &str = "http://127.0.0.1:9090";
const WAIT_SECS: u64 = 320;
let temp = tempfile::tempdir()?;
let temp_dir = temp.path();
//let temp_dir = Path::new("./"); //For testing locally
// pop new contract test_contract (default)
Command::cargo_bin("pop")
.unwrap()
.current_dir(&temp_dir)
.args(&["new", "contract", "test_contract"])
.assert()
.success();
assert!(temp_dir.join("test_contract").exists());

let binary = contracts_node_generator(temp_dir.to_path_buf().clone(), None).await?;
binary.source(false, &(), true).await?;
set_executable_permission(binary.path())?;
let process = run_contracts_node(binary.path(), None, DEFAULT_PORT).await?;
sleep(Duration::from_secs(10)).await;

// pop up contract --upload-only --use-wallet
// Using `cargo run --` as means for the CI to pass.
// Possibly there's room for improvement here.
let _handler = tokio::process::Command::new("cargo")
.args(&[
"run",
"--",
"up",
"contract",
"--upload-only",
"--use-wallet",
"--skip-confirm",
"--dry-run",
"--url",
DEFAULT_ENDPOINT,
"-p",
temp_dir.join("test_contract").to_str().expect("to_str"),
])
.spawn()?;
// Wait a moment for node and server to be up.
sleep(Duration::from_secs(WAIT_SECS)).await;

// Request payload from server.
let response = reqwest::get(&format!("{}/payload", WALLET_INT_URI))
.await
.expect("Failed to get payload")
.json::<TransactionData>()
.await
.expect("Failed to parse payload");
// We have received some payload.
assert!(!response.call_data().is_empty());

let rpc_client = subxt::backend::rpc::RpcClient::from_url(DEFAULT_ENDPOINT).await?;
let client = subxt::OnlineClient::<subxt::SubstrateConfig>::from_rpc_client(rpc_client).await?;

// Sign payload.
let signer = dev::alice();
let payload = CallData(response.call_data());
let ext_params = Params::new().build();
let signed = client.tx().create_signed(&payload, &signer, ext_params).await?;

// Submit signed payload. This kills the wallet integration server.
let _ = reqwest::Client::new()
.post(&format!("{}/submit", WALLET_INT_URI))
.json(&to_hex(signed.encoded()))
.send()
.await
.expect("Failed to submit payload")
.text()
.await
.expect("Failed to parse JSON response");

// Request payload from server after signed payload has been sent.
// Server should not be running!
let response = reqwest::get(&format!("{}/payload", WALLET_INT_URI)).await;
assert!(response.is_err());

// Stop the process contracts-node
Cmd::new("kill")
.args(["-s", "TERM", &process.id().to_string()])
.spawn()?
.wait()?;

Ok(())
}

fn generate_all_the_templates(temp_dir: &Path) -> Result<()> {
for template in Contract::VARIANTS {
let contract_name = format!("test_contract_{}", template).replace("-", "_");
Expand Down
3 changes: 2 additions & 1 deletion crates/pop-contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ subxt.workspace = true
# cargo-contracts
contract-build.workspace = true
contract-extrinsics.workspace = true
contract-transcode.workspace = true
contract-transcode.workspace = true
scale-info.workspace = true
# pop
pop-common = { path = "../pop-common", version = "0.5.0" }
Expand All @@ -42,3 +42,4 @@ pop-common = { path = "../pop-common", version = "0.5.0" }
dirs.workspace = true
mockito.workspace = true
tokio-test.workspace = true
hex.workspace = true
Loading
Loading