diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b65d0b0..daee80d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,12 +9,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y libudev-dev pkg-config - - name: Install cargo-near - run: cargo install --locked cargo-near + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh - name: Install and test modules run: | diff --git a/Cargo.toml b/Cargo.toml index 7df05bc..53407f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,24 @@ crate-type = ["cdylib", "rlib"] [dependencies] borsh = "1.5.7" bs58 = "0.5.1" -near-sdk = { version = "5.17.1", features = ["global-contracts", "unstable"] } +near-sdk = { version = "5.24.0", features = ["global-contracts"] } [dev-dependencies] -near-sdk = { version = "5.17.1", features = ["global-contracts", "unit-testing"] } -near-workspaces = { version = "0.21", features = ["unstable"] } -tokio = { version = "1.12.0", features = ["full"] } +near-sdk = { version = "5.24.0", features = ["unit-testing"] } +near-sandbox = "0.3" +near-api = "0.8" +cargo-near-build = "=0.10" +tokio = { version = "1.48.0", features = ["full"] } +serde_json = "1" +testresult = "0.4.1" anyhow = "1.0" +# This is temporary fix for the build error since those crates with a higher version require a higher version of Rust compiler (1.88.0) +cargo-platform = "=0.3.1" +darling = "=0.20.11" +bon = "=3.8.1" +home = "=0.5.9" +time = "=0.3.44" +time-core = "=0.1.6" [profile.release] codegen-units = 1 diff --git a/src/lib.rs b/src/lib.rs index 1ac6706..ee30f6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,9 @@ impl GlobalFactoryContract { "Using global contract with code hash: {:?}", code_hash )); - promise.use_global_contract(bs58::decode(code_hash).into_vec().unwrap()) + let code_hash_vec = bs58::decode(code_hash).into_vec().unwrap(); + let code_hash_vec_array: [u8; 32] = code_hash_vec.try_into().unwrap(); + promise.use_global_contract(code_hash_vec_array) } } } diff --git a/tests/test_basics.rs b/tests/test_basics.rs new file mode 100644 index 0000000..c3fa27d --- /dev/null +++ b/tests/test_basics.rs @@ -0,0 +1,180 @@ +use factory_contract_global::GlobalContractId; +use near_api::AccountId; +use near_sdk::{serde_json::json, NearToken}; + +const DEFAULT_GLOBAL_CONTRACT_ACCOUNT_ID: &str = "ft.globals.primitives.testnet"; + +const TEST_GLOBAL_CONTRACT_ACCOUNT_ID: &str = "ft.globals.primitives.testnet"; +const TEST_GLOBAL_CONTRACT_HASH: &str = "3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"; +const TEST_DEPOSIT_AMOUNT: u128 = 100; // 0.1 NEAR + +/// TODO: add tests for deploy method as soon as near-workspaces-rs supports deploying global contracts. +/// Currently it does not, therefore it's impossible to deploy global contract to use it in tests. + +/// Test management of global contract ID +#[tokio::test] +async fn test_manager() -> anyhow::Result<()> { + let sandbox = near_sandbox::Sandbox::start_sandbox().await?; + let sandbox_network = + near_api::NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?); + let factory_wasm_path = cargo_near_build::build_with_cli(Default::default()).unwrap(); + let factory_wasm = std::fs::read(factory_wasm_path)?; + + let factory_contract = create_subaccount(&sandbox, "factory.sandbox") + .await + .unwrap() + .as_contract(); + + // Initialize signer for the contract deployment + let signer = near_api::Signer::from_secret_key( + near_sandbox::config::DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY + .parse() + .unwrap(), + )?; + + // Deploy the base contract + near_api::Contract::deploy(factory_contract.account_id().clone()) + .use_code(factory_wasm) + .without_init_call() + .with_signer(signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_success(); + + let default_contract_id: GlobalContractId = factory_contract + .call_function("get_global_contract_id", ()) + .read_only() + .fetch_from(&sandbox_network) + .await? + .data; + assert_eq!( + default_contract_id, + GlobalContractId::AccountId(DEFAULT_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()) + ); + + factory_contract + .call_function( + "update_global_contract_id", + json!({ + "contract_id": GlobalContractId::CodeHash(TEST_GLOBAL_CONTRACT_HASH.to_string()), + "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) + }), + ) + .transaction() + .max_gas() + .with_signer(factory_contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_success(); + + let global_contract_id: GlobalContractId = factory_contract + .call_function("get_global_contract_id", ()) + .read_only() + .fetch_from(&sandbox_network) + .await? + .data; + assert_eq!( + global_contract_id, + GlobalContractId::CodeHash(TEST_GLOBAL_CONTRACT_HASH.to_string()) + ); + + factory_contract + .call_function("update_global_contract_id", json!({ + "contract_id": GlobalContractId::AccountId(TEST_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()), + "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) + })) + .transaction() + .max_gas() + .with_signer(factory_contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_success(); + + let global_contract_id: GlobalContractId = factory_contract + .call_function("get_global_contract_id", ()) + .read_only() + .fetch_from(&sandbox_network) + .await? + .data; + assert_eq!( + global_contract_id, + GlobalContractId::AccountId(TEST_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()) + ); + + let min_deposit: NearToken = factory_contract + .call_function("get_min_deposit", ()) + .read_only() + .fetch_from(&sandbox_network) + .await? + .data; + assert!(min_deposit.eq(&NearToken::from_millinear(TEST_DEPOSIT_AMOUNT))); + Ok(()) +} + +/// Test error cases and edge conditions +#[tokio::test] +async fn test_global_contract_edge_cases() -> anyhow::Result<()> { + let sandbox = near_sandbox::Sandbox::start_sandbox().await?; + let sandbox_network = + near_api::NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?); + let factory_wasm_path = cargo_near_build::build_with_cli(Default::default()).unwrap(); + let factory_wasm = std::fs::read(factory_wasm_path)?; + + let factory_contract = create_subaccount(&sandbox, "factory.sandbox") + .await + .unwrap() + .as_contract(); + + // Initialize signer for the contract deployment + let signer = near_api::Signer::from_secret_key( + near_sandbox::config::DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY + .parse() + .unwrap(), + )?; + + // Deploy the base contract + near_api::Contract::deploy(factory_contract.account_id().clone()) + .use_code(factory_wasm) + .without_init_call() + .with_signer(signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_success(); + + factory_contract + .call_function("update_global_contract_id", json!({ + "contract_id": GlobalContractId::CodeHash("11111111111111111111111111111111".to_string()), + "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) + })) + .transaction() + .max_gas() + .with_signer(factory_contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_success(); + + // Test using non-existent global contract + factory_contract + .call_function("deploy", json!({ "name": "new_ft" })) + .transaction() + .max_gas() + .with_signer(factory_contract.account_id().clone(), signer.clone()) + .send_to(&sandbox_network) + .await? + .assert_failure(); + + Ok(()) +} + +async fn create_subaccount( + sandbox: &near_sandbox::Sandbox, + name: &str, +) -> testresult::TestResult { + let account_id: AccountId = name.parse().unwrap(); + sandbox + .create_account(account_id.clone()) + .initial_balance(NearToken::from_near(10)) + .send() + .await?; + Ok(near_api::Account(account_id)) +} diff --git a/tests/workspaces.rs b/tests/workspaces.rs deleted file mode 100644 index 2d7a32e..0000000 --- a/tests/workspaces.rs +++ /dev/null @@ -1,118 +0,0 @@ -use factory_contract_global::GlobalContractId; -use near_sdk::{serde_json::json, NearToken}; - -const DEFAULT_GLOBAL_CONTRACT_ACCOUNT_ID: &str = "ft.globals.primitives.testnet"; - -const TEST_GLOBAL_CONTRACT_ACCOUNT_ID: &str = "ft.globals.primitives.testnet"; -const TEST_GLOBAL_CONTRACT_HASH: &str = "3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"; -const TEST_DEPOSIT_AMOUNT: u128 = 100; // 0.1 NEAR - -/// TODO: add tests for deploy method as soon as near-workspaces-rs supports deploying global contracts. -/// Currently it does not, therefore it's impossible to deploy global contract to use it in tests. - -/// Test management of global contract ID -#[tokio::test] -async fn test_manager() -> anyhow::Result<()> { - let worker = near_workspaces::sandbox_with_version("2.7.0").await?; - let factory_wasm = near_workspaces::compile_project(".").await?; - let factory_contract = worker.dev_deploy(&factory_wasm).await?; - - let default_contract_id = factory_contract - .call("get_global_contract_id") - .view() - .await? - .json::>()? - .expect("Should have stored global contract ID"); - assert_eq!( - default_contract_id, - GlobalContractId::AccountId(DEFAULT_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()) - ); - - let change_contract_id_res_1 = factory_contract - .call("update_global_contract_id") - .args_json(json!({ - "contract_id": GlobalContractId::CodeHash(TEST_GLOBAL_CONTRACT_HASH.to_string()), - "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) - })) - .max_gas() - .transact() - .await?; - assert!(change_contract_id_res_1.is_success()); - - let global_contract_id = factory_contract - .call("get_global_contract_id") - .view() - .await? - .json::>()? - .expect("Should have stored global contract ID"); - assert_eq!( - global_contract_id, - GlobalContractId::CodeHash(TEST_GLOBAL_CONTRACT_HASH.to_string()) - ); - - let change_contract_id_res_2 = factory_contract - .call("update_global_contract_id") - .args_json(json!({ - "contract_id": GlobalContractId::AccountId(TEST_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()), - "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) - })) - .max_gas() - .transact() - .await?; - assert!(change_contract_id_res_2.is_success()); - - let global_contract_id = factory_contract - .call("get_global_contract_id") - .view() - .await? - .json::>()? - .expect("Should have stored global contract ID"); - assert_eq!( - global_contract_id, - GlobalContractId::AccountId(TEST_GLOBAL_CONTRACT_ACCOUNT_ID.parse().unwrap()) - ); - - let min_deposit = factory_contract - .call("get_min_deposit") - .args_json(()) - .view() - .await? - .json::>()? - .expect("Should have stored global contract ID"); - assert!(min_deposit.eq(&NearToken::from_millinear(TEST_DEPOSIT_AMOUNT))); - Ok(()) -} - -/// Test error cases and edge conditions -#[tokio::test] -async fn test_global_contract_edge_cases() -> anyhow::Result<()> { - let worker = near_workspaces::sandbox_with_version("2.7.0").await?; - let factory_wasm = near_workspaces::compile_project(".").await?; - let factory_contract = worker.dev_deploy(&factory_wasm).await?; - - let change_contract_id_res = factory_contract - .call("update_global_contract_id") - .args_json( - json!({ - "contract_id": GlobalContractId::CodeHash("11111111111111111111111111111111".to_string()), - "min_deposit": NearToken::from_millinear(TEST_DEPOSIT_AMOUNT) } - )) - .max_gas() - .transact() - .await?; - assert!(change_contract_id_res.is_success()); - - // Test using non-existent global contract - let res = factory_contract - .call("deploy") - .args_json(("new_ft",)) - .max_gas() - .transact() - .await?; - assert!( - res.is_failure(), - "Not failed to use global contract by hash: {res:?}" - ); - - Ok(()) -}