use git2::{Cred, RemoteCallbacks}; use itertools::Itertools; use near_client::{ client::{NearClient, Signer}, types::{ crypto::{ED25519PublicKey, ED25519SecretKey, Key}, near::{ViewAccessKey, ViewAccessKeyResult}, }, }; use rand::{RngCore, SeedableRng}; use rand_chacha::ChaChaRng; use reqwest::Url; use serde_json::json; use std::{ fs::{create_dir, read, write}, path::Path, str::FromStr, }; use workspaces::{network::Sandbox, types::SecretKey, AccountId, Worker}; // auxiliary structs and methods fn near_client(worker: &Worker) -> anyhow::Result { let rpc_url = Url::parse(format!("http://localhost:{}", worker.rpc_port()).as_str())?; let client = NearClient::new(rpc_url)?; Ok(client) } async fn create_signer( worker: &Worker, client: &NearClient, signer_acc_id: &AccountId, ) -> anyhow::Result { let secret_key = ED25519SecretKey::from_bytes(&random_bits())?; let ws_secret_key_str = to_workspaces_sk(&secret_key); let pk = ED25519PublicKey::from(&secret_key); let ws_sk = SecretKey::from_str(&ws_secret_key_str)?; let _ = worker.create_tla(signer_acc_id.clone(), ws_sk).await?; client .view_access_key(signer_acc_id, &pk) .await .map(|access_key_view| match access_key_view.result { ViewAccessKeyResult::Ok { nonce, .. } => { let signer_acc = Signer::new(&ws_secret_key_str, signer_acc_id.clone(), nonce)?; Ok(signer_acc) } ViewAccessKeyResult::Err { error, .. } => Err(anyhow::anyhow!(error)), })? } async fn download_contract() -> anyhow::Result> { let target = "https://github.com/near-examples/FT/raw/master/res/fungible_token.wasm"; let target_path = "../target/tmp-contracts"; let fname = "contract.wasm"; let full_dest = format!("{}/{}", target_path, fname); let contract_bytes = if !Path::new(&full_dest).exists() { if !Path::new(&target_path).exists() { create_dir(target_path)?; }; let contract_bytes = reqwest::get(target).await?.bytes().await?; write(full_dest, &contract_bytes)?; contract_bytes.to_vec() } else { read(&full_dest)? }; Ok(contract_bytes) } async fn clone_and_compile_wasm() -> anyhow::Result> { let tmp_path = "../target/tmp-contracts"; let repo_path = format!("{}/near-smartcontracts", tmp_path); let target_path = format!("{}/test-contract", repo_path); let repo_url = "git@github.com:Relayz-io/near-smartcontracts.git"; if !Path::new(&target_path).exists() { if !Path::new(&tmp_path).exists() { create_dir(tmp_path)?; } // Prepare callbacks. let mut callbacks = RemoteCallbacks::new(); callbacks.credentials(|_, username_from_url, _| { let username = username_from_url .ok_or_else(|| git2::Error::from_str("Parsing the given URL is failed"))?; Cred::ssh_key_from_agent(username) }); // Prepare fetch options. let mut fo = git2::FetchOptions::new(); fo.remote_callbacks(callbacks); // Prepare builder. let mut builder = git2::build::RepoBuilder::new(); builder.fetch_options(fo); builder.branch("main"); // Clone the project. builder.clone(repo_url, Path::new(&repo_path))?; } let wasm = workspaces::compile_project(&target_path).await?; Ok(wasm) } fn random_bits() -> [u8; 32] { let mut chacha = ChaChaRng::from_entropy(); let mut secret_bytes = [0_u8; 32]; chacha.fill_bytes(&mut secret_bytes); secret_bytes } fn to_workspaces_sk(sk: &ED25519SecretKey) -> String { let pk = ED25519PublicKey::from(sk); let keypair = sk .as_bytes() .iter() .chain(pk.as_bytes().iter()) .copied() .collect_vec(); format!("ed25519:{}", bs58::encode(keypair).into_string()) } // tests themselves #[tokio::test] async fn contract_creation() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let wasm = download_contract().await?; client .deploy_contract(&mut signer, &signer_account_id, wasm) .commit_empty() .await?; Ok(()) } #[tokio::test] async fn contract_function_call() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let wasm = download_contract().await?; client .deploy_contract(&mut signer, &signer_account_id, wasm) .commit_empty() .await?; client .function_call(&mut signer, &signer_account_id, "new_default_meta") .args(json!({ "owner_id": &signer_account_id, "total_supply": "100", })) .gas(near_units::parse_gas!("300 T") as u64) .build()? .commit_empty() .await?; Ok(()) } #[tokio::test] async fn multiple_tests() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let wasm = clone_and_compile_wasm().await?; init_contract(&client, &signer_account_id, &mut signer, wasm).await?; fc_no_params(&client, &signer_account_id, &mut signer).await?; fc_with_one_param_and_result(&client, &signer_account_id, &mut signer).await?; fc_with_param_and_result(&client, &signer_account_id, &mut signer).await?; view_no_params(&client, &signer_account_id).await?; view_with_params(&client, &signer_account_id).await?; Ok(()) } async fn init_contract( client: &NearClient, contract_id: &AccountId, signer: &mut Signer, wasm: Vec, ) -> anyhow::Result<()> { client .deploy_contract(signer, contract_id, wasm) .commit_empty() .await?; Ok(()) } async fn view_no_params(client: &NearClient, contract_id: &AccountId) -> anyhow::Result<()> { client.view::(contract_id, "show_id", None).await?; Ok(()) } async fn view_with_params(client: &NearClient, contract_id: &AccountId) -> anyhow::Result<()> { client .view::(contract_id, "show_type", Some(json!({"is_message": true}))) .await?; Ok(()) } // fc = function call async fn fc_no_params( client: &NearClient, contract_id: &AccountId, signer: &mut Signer, ) -> anyhow::Result<()> { client .function_call(signer, contract_id, "increment") .gas(near_units::parse_gas!("300 T") as u64) .build()? .commit_empty() .await?; Ok(()) } async fn fc_with_one_param_and_result( client: &NearClient, contract_id: &AccountId, signer: &mut Signer, ) -> anyhow::Result<()> { let expected_result = "change message"; let message = client .function_call(signer, contract_id, "change_message") .args(json!({ "message": expected_result })) .gas(near_units::parse_gas!("300 T") as u64) .build()? .commit::() .await?; assert_eq!(message, expected_result); Ok(()) } async fn fc_with_param_and_result( client: &NearClient, contract_id: &AccountId, signer: &mut Signer, ) -> anyhow::Result<()> { let expected_id = 666u64; let id = client .function_call(signer, contract_id, "change_id") .args(json!({ "id": expected_id })) .gas(near_units::parse_gas!("300 T") as u64) .build()? .commit::() .await?; assert_eq!(id, expected_id); Ok(()) } #[tokio::test] async fn view_access_key_success() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let new_acc = AccountId::from_str("one.alice.test.near")?; let secret_key = ED25519SecretKey::from_bytes(&random_bits())?; let pk = ED25519PublicKey::from(&secret_key); let _ = client .create_account(&mut signer, &new_acc, pk, near_units::parse_near!("3 N")) .commit::() .await?; let access_key = client.view_access_key(&new_acc, &pk).await?; assert!(matches!( access_key, ViewAccessKey { result: ViewAccessKeyResult::Ok { .. }, .. } )); Ok(()) } #[tokio::test] async fn view_access_key_failure() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let new_acc = AccountId::from_str("one.alice.test.near")?; let secret_key = ED25519SecretKey::from_bytes(&random_bits())?; let pk = ED25519PublicKey::from(&secret_key); let access_key = client.view_access_key(&new_acc, &pk).await?; assert!(matches!( access_key, ViewAccessKey { result: ViewAccessKeyResult::Err { .. }, .. } )); Ok(()) } #[tokio::test] async fn create_account() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let new_acc = AccountId::from_str("one.alice.test.near")?; let secret_key = ED25519SecretKey::from_bytes(&random_bits())?; let pk = ED25519PublicKey::from(&secret_key); let _ = client .create_account(&mut signer, &new_acc, pk, near_units::parse_near!("3 N")) .commit::() .await?; let access_key = client.view_access_key(&new_acc, &pk).await?; assert!(matches!( access_key, ViewAccessKey { result: ViewAccessKeyResult::Ok { .. }, .. } )); Ok(()) } #[tokio::test] async fn delete_account() -> anyhow::Result<()> { let worker = workspaces::sandbox().await?; let client = near_client(&worker)?; let signer_account_id = AccountId::from_str("alice.test.near").unwrap(); let mut signer = create_signer(&worker, &client, &signer_account_id).await?; let new_acc = AccountId::from_str("one.alice.test.near")?; let secret_key = ED25519SecretKey::from_bytes(&random_bits())?; let pk = ED25519PublicKey::from(&secret_key); client .create_account(&mut signer, &new_acc, pk, near_units::parse_near!("3 N")) .commit_empty() .await?; let access_key = client.view_access_key(&new_acc, &pk).await?; let nonce = if let ViewAccessKey { result: ViewAccessKeyResult::Ok { nonce, .. }, .. } = access_key { nonce } else { panic!("Can't view access key for just created account") }; let mut acc_signer = Signer::new(&secret_key.to_string(), new_acc.clone(), nonce)?; client .delete_account(&mut acc_signer, &new_acc, &signer_account_id) .commit_empty() .await?; let access_key = client.view_access_key(&new_acc, &pk).await?; assert!(matches!( access_key, ViewAccessKey { result: ViewAccessKeyResult::Err { .. }, .. } )); Ok(()) }