From db88f8dc73f7ab139c434db070cb846c36bfd9b7 Mon Sep 17 00:00:00 2001 From: Pavel Kisialiou <30559865+kisialiou@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:10:43 +0300 Subject: [PATCH] Add integration tests for OmniToken. (#148) * Add integration tests for OmniToken. * Correct chain kind for deploy token args. * Add the tests for the native token. --- near/omni-tests/src/fin_transfer.rs | 28 +- near/omni-tests/src/helpers.rs | 53 +++- near/omni-tests/src/init_transfer.rs | 4 +- near/omni-tests/src/lib.rs | 1 + near/omni-tests/src/omni_token.rs | 415 +++++++++++++++++++++++++++ 5 files changed, 476 insertions(+), 25 deletions(-) create mode 100644 near/omni-tests/src/omni_token.rs diff --git a/near/omni-tests/src/fin_transfer.rs b/near/omni-tests/src/fin_transfer.rs index 206e22b9..a94fd847 100644 --- a/near/omni-tests/src/fin_transfer.rs +++ b/near/omni-tests/src/fin_transfer.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod tests { use crate::helpers::tests::{ - account_1, account_2, eth_eoa_address, eth_factory_address, relayer_account_id, - LOCKER_PATH, MOCK_PROVER_PATH, MOCK_TOKEN_PATH, NEP141_DEPOSIT, + account_n, eth_eoa_address, eth_factory_address, relayer_account_id, LOCKER_PATH, + MOCK_PROVER_PATH, MOCK_TOKEN_PATH, NEP141_DEPOSIT, }; use near_sdk::{borsh, json_types::U128, serde_json::json, AccountId}; use near_workspaces::types::NearToken; @@ -14,46 +14,46 @@ mod tests { use rstest::rstest; #[rstest] - #[case(vec![(account_1(), true), (relayer_account_id(), true)], 1000, 1, None)] - #[case(vec![(account_1(), true)], 1000, 0, None)] + #[case(vec![(account_n(1), true), (relayer_account_id(), true)], 1000, 1, None)] + #[case(vec![(account_n(1), true)], 1000, 0, None)] #[case( vec![ - (account_1(), true), + (account_n(1), true), (relayer_account_id(), true), - (account_2(), true), - (account_2(), true), + (account_n(2), true), + (account_n(2), true), ], 1000, 1, Some("Invalid len of accounts for storage deposit") )] #[case( - vec![(relayer_account_id(), true), (account_1(), true)], + vec![(relayer_account_id(), true), (account_n(1), true)], 1000, 1, Some("STORAGE_ERR: The transfer recipient is omitted") )] #[case( - vec![(account_1(), true)], + vec![(account_n(1), true)], 1000, 1, Some("STORAGE_ERR: The fee recipient is omitted") )] #[case(vec![], 1000, 1, Some("STORAGE_ERR: The transfer recipient is omitted"))] #[case( - vec![(account_1(), false), (relayer_account_id(), false)], + vec![(account_n(1), false), (relayer_account_id(), false)], 1000, 1, Some("STORAGE_ERR: The transfer recipient is omitted") )] #[case( - vec![(account_1(), true), (relayer_account_id(), false)], + vec![(account_n(1), true), (relayer_account_id(), false)], 1000, 1, Some("STORAGE_ERR: The fee recipient is omitted") )] #[case( - vec![(account_1(), false), (relayer_account_id(), true)], + vec![(account_n(1), false), (relayer_account_id(), true)], 1000, 1, Some("STORAGE_ERR: The transfer recipient is omitted") @@ -188,7 +188,7 @@ mod tests { prover_args: borsh::to_vec(&ProverResult::InitTransfer(InitTransferMessage { origin_nonce: 1, token: OmniAddress::Near(token_contract.id().clone()), - recipient: OmniAddress::Near(account_1()), + recipient: OmniAddress::Near(account_n(1)), amount: U128(amount), fee: Fee { fee: U128(fee), @@ -210,7 +210,7 @@ mod tests { let recipient_balance: U128 = token_contract .view("ft_balance_of") .args_json(json!({ - "account_id": account_1(), + "account_id": account_n(1), })) .await? .json()?; diff --git a/near/omni-tests/src/helpers.rs b/near/omni-tests/src/helpers.rs index dc2e8f8f..6b33d05a 100644 --- a/near/omni-tests/src/helpers.rs +++ b/near/omni-tests/src/helpers.rs @@ -3,9 +3,9 @@ pub mod tests { use near_sdk::{borsh, json_types::U128, serde_json, AccountId}; use near_workspaces::types::NearToken; use omni_types::{ - locker_args::{BindTokenArgs, ClaimFeeArgs}, - prover_result::{DeployTokenMessage, FinTransferMessage, ProverResult}, - ChainKind, Nonce, OmniAddress, TransferId, + locker_args::{BindTokenArgs, ClaimFeeArgs, DeployTokenArgs}, + prover_result::{DeployTokenMessage, FinTransferMessage, LogMetadataMessage, ProverResult}, + BasicMetadata, ChainKind, Nonce, OmniAddress, TransferId, }; pub const MOCK_TOKEN_PATH: &str = "./../target/wasm32-unknown-unknown/release/mock_token.wasm"; @@ -13,17 +13,15 @@ pub mod tests { "./../target/wasm32-unknown-unknown/release/mock_prover.wasm"; pub const LOCKER_PATH: &str = "./../target/wasm32-unknown-unknown/release/omni_bridge.wasm"; pub const NEP141_DEPOSIT: NearToken = NearToken::from_yoctonear(1250000000000000000000); + pub const TOKEN_DEPLOYER_PATH: &str = + "./../target/wasm32-unknown-unknown/release/token_deployer.wasm"; pub fn relayer_account_id() -> AccountId { "relayer".parse().unwrap() } - pub fn account_1() -> AccountId { - "account_1".parse().unwrap() - } - - pub fn account_2() -> AccountId { - "account_2".parse().unwrap() + pub fn account_n(n: u8) -> AccountId { + format!("account_{}", n).parse().unwrap() } pub fn eth_factory_address() -> OmniAddress { @@ -44,6 +42,22 @@ pub mod tests { .unwrap() } + pub fn sol_token_address() -> OmniAddress { + "sol:11111111111111111111111111111111".parse().unwrap() + } + + pub fn arb_token_address() -> OmniAddress { + "arb:0x1234567890123456789012345678901234567890" + .parse() + .unwrap() + } + + pub fn base_token_address() -> OmniAddress { + "base:0x1234567890123456789012345678901234567890" + .parse() + .unwrap() + } + pub fn get_claim_fee_args_near( origin_chain: ChainKind, destination_chain: ChainKind, @@ -72,6 +86,27 @@ pub mod tests { } } + pub fn get_test_deploy_token_args( + token_address: &OmniAddress, + token_metadata: &BasicMetadata, + ) -> DeployTokenArgs { + let log_metadata_message = LogMetadataMessage { + token_address: token_address.clone(), + name: token_metadata.name.clone(), + symbol: token_metadata.symbol.clone(), + decimals: token_metadata.decimals, + emitter_address: token_address.clone(), + }; + + let prover_result = ProverResult::LogMetadata(log_metadata_message); + let prover_args = borsh::to_vec(&prover_result).expect("Failed to serialize prover result"); + + DeployTokenArgs { + chain_kind: token_address.get_chain(), + prover_args, + } + } + pub fn get_bind_token_args( token: &AccountId, token_address: &OmniAddress, diff --git a/near/omni-tests/src/init_transfer.rs b/near/omni-tests/src/init_transfer.rs index 28c74d90..560c9ca9 100644 --- a/near/omni-tests/src/init_transfer.rs +++ b/near/omni-tests/src/init_transfer.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests { use crate::helpers::tests::{ - account_1, eth_eoa_address, eth_factory_address, eth_token_address, get_bind_token_args, + account_n, eth_eoa_address, eth_factory_address, eth_token_address, get_bind_token_args, get_claim_fee_args_near, get_event_data, relayer_account_id, LOCKER_PATH, MOCK_PROVER_PATH, MOCK_TOKEN_PATH, NEP141_DEPOSIT, }; @@ -82,7 +82,7 @@ mod tests { // Create sender account. (Default account in sandbox has 100 NEAR) let sender_account = worker - .create_tla(account_1(), worker.dev_generate().await.1) + .create_tla(account_n(1), worker.dev_generate().await.1) .await? .unwrap(); diff --git a/near/omni-tests/src/lib.rs b/near/omni-tests/src/lib.rs index a5a971b0..1717cf35 100644 --- a/near/omni-tests/src/lib.rs +++ b/near/omni-tests/src/lib.rs @@ -1,3 +1,4 @@ mod fin_transfer; mod helpers; mod init_transfer; +mod omni_token; diff --git a/near/omni-tests/src/omni_token.rs b/near/omni-tests/src/omni_token.rs new file mode 100644 index 00000000..6ac564eb --- /dev/null +++ b/near/omni-tests/src/omni_token.rs @@ -0,0 +1,415 @@ +#[cfg(test)] +mod tests { + use crate::helpers::tests::{ + account_n, arb_token_address, base_token_address, eth_eoa_address, eth_token_address, + get_test_deploy_token_args, sol_token_address, LOCKER_PATH, MOCK_PROVER_PATH, + NEP141_DEPOSIT, TOKEN_DEPLOYER_PATH, + }; + use anyhow; + use near_sdk::borsh; + use near_sdk::json_types::U128; + use near_sdk::serde_json::json; + use near_workspaces::{types::NearToken, AccountId}; + use omni_types::locker_args::{FinTransferArgs, StorageDepositAction}; + use omni_types::prover_result::InitTransferMessage; + use omni_types::prover_result::ProverResult; + use omni_types::Fee; + use omni_types::{BasicMetadata, ChainKind, OmniAddress}; + use rstest::rstest; + use std::str::FromStr; + + struct TestEnv { + worker: near_workspaces::Worker, + locker: near_workspaces::Contract, + token_contract: near_workspaces::Contract, + init_token_address: OmniAddress, + token_metadata: BasicMetadata, + } + + impl TestEnv { + async fn new(init_token_address: OmniAddress) -> anyhow::Result { + let worker = near_workspaces::sandbox().await?; + let token_metadata = BasicMetadata { + name: "Test Token".to_string(), + symbol: "TEST".to_string(), + decimals: 18, + }; + + // Setup prover + let prover_contract = worker.dev_deploy(&std::fs::read(MOCK_PROVER_PATH)?).await?; + + // Setup locker + let locker = worker.dev_deploy(&std::fs::read(LOCKER_PATH)?).await?; + locker + .call("new") + .args_json(json!({ + "prover_account": prover_contract.id(), + "mpc_signer": "mpc.testnet", + "nonce": U128(0), + "wnear_account_id": "wnear.testnet", + })) + .max_gas() + .transact() + .await? + .into_result()?; + + // Setup token deployer + let token_deployer = worker + .create_tla_and_deploy( + account_n(1), + worker.dev_generate().await.1, + &std::fs::read(TOKEN_DEPLOYER_PATH)?, + ) + .await? + .unwrap(); + + token_deployer + .call("new") + .args_json(json!({ + "controller": locker.id(), + "dao": AccountId::from_str("dao.near").unwrap(), + })) + .max_gas() + .transact() + .await? + .into_result()?; + + // Configure locker + locker + .call("add_token_deployer") + .args_json(json!({ + "chain": init_token_address.get_chain(), + "account_id": token_deployer.id(), + })) + .max_gas() + .transact() + .await? + .into_result()?; + + locker + .call("add_factory") + .args_json(json!({ + "address": init_token_address, + })) + .max_gas() + .transact() + .await? + .into_result()?; + + // Deploy token + let token_contract = + Self::deploy_token(&worker, &locker, &init_token_address, &token_metadata).await?; + + Ok(Self { + worker, + locker, + token_contract, + init_token_address, + token_metadata, + }) + } + + async fn new_native(chain_kind: ChainKind) -> anyhow::Result { + let init_token_address = OmniAddress::new_zero(chain_kind).unwrap(); + Self::new(init_token_address).await + } + + async fn deploy_token( + worker: &near_workspaces::Worker, + locker: &near_workspaces::Contract, + init_token_address: &OmniAddress, + token_metadata: &BasicMetadata, + ) -> anyhow::Result { + let token_deploy_initiator = worker + .create_tla(account_n(2), worker.dev_generate().await.1) + .await? + .unwrap(); + + let required_storage: NearToken = locker + .view("required_balance_for_deploy_token") + .await? + .json()?; + + if init_token_address == &OmniAddress::new_zero(init_token_address.get_chain()).unwrap() + { + locker + .call("deploy_native_token") + .args_json(json!({ + "chain_kind": init_token_address.get_chain(), + "name": token_metadata.name, + "symbol": token_metadata.symbol, + "decimals": token_metadata.decimals, + })) + .deposit(required_storage) + .max_gas() + .transact() + .await? + .into_result()?; + } else { + token_deploy_initiator + .call(locker.id(), "deploy_token") + .args_borsh(get_test_deploy_token_args( + init_token_address, + token_metadata, + )) + .deposit(required_storage) + .max_gas() + .transact() + .await? + .into_result()?; + } + + let token_account_id: AccountId = locker + .view("get_token_id") + .args_json(json!({ + "address": init_token_address + })) + .await? + .json()?; + + let token_contract = worker + .import_contract(&token_account_id, worker) + .transact() + .await?; + + Ok(token_contract) + } + + // Helper to create and register a new account + async fn create_registered_account( + &self, + account_num: u8, + ) -> anyhow::Result { + let account = self + .worker + .create_tla(account_n(account_num), self.worker.dev_generate().await.1) + .await? + .unwrap(); + + self.token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": Some(account.id()), + "registration_only": Some(true), + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + Ok(account) + } + } + + #[rstest] + #[case(eth_token_address(), false)] + #[case(sol_token_address(), false)] + #[case(arb_token_address(), false)] + #[case(base_token_address(), false)] + #[case(eth_token_address(), true)] + #[case(sol_token_address(), true)] + #[case(arb_token_address(), true)] + #[case(base_token_address(), true)] + #[tokio::test] + async fn test_token_metadata( + #[case] init_token_address: OmniAddress, + #[case] is_native: bool, + ) -> anyhow::Result<()> { + let env = if is_native { + TestEnv::new_native(init_token_address.get_chain()).await? + } else { + TestEnv::new(init_token_address).await? + }; + + let fetched_metadata: BasicMetadata = + env.token_contract.view("ft_metadata").await?.json()?; + + assert_eq!(env.token_metadata.name, fetched_metadata.name); + assert_eq!(env.token_metadata.symbol, fetched_metadata.symbol); + assert_eq!(env.token_metadata.decimals, fetched_metadata.decimals); + + Ok(()) + } + + #[rstest] + #[case(eth_token_address(), false)] + #[case(sol_token_address(), false)] + #[case(arb_token_address(), false)] + #[case(base_token_address(), false)] + #[case(eth_token_address(), true)] + #[case(sol_token_address(), true)] + #[case(arb_token_address(), true)] + #[case(base_token_address(), true)] + #[tokio::test] + async fn test_token_minting( + #[case] init_token_address: OmniAddress, + #[case] is_native: bool, + ) -> anyhow::Result<()> { + let env = if is_native { + TestEnv::new_native(init_token_address.get_chain()).await? + } else { + TestEnv::new(init_token_address).await? + }; + let recipient = env.create_registered_account(3).await?; + let amount = U128(1000000000000000000000000); + + fake_finalize_transfer( + &env.locker, + &env.token_contract, + &recipient, + env.init_token_address, + amount, + ) + .await?; + + let balance: U128 = env + .token_contract + .view("ft_balance_of") + .args_json(json!({ + "account_id": recipient.id(), + })) + .await? + .json()?; + + let total_supply: U128 = env.token_contract.view("ft_total_supply").await?.json()?; + + assert_eq!( + balance, amount, + "Balance should be equal to the minted amount" + ); + assert_eq!( + total_supply, amount, + "Total supply should be equal to the minted amount" + ); + Ok(()) + } + + #[rstest] + #[case(eth_token_address(), false)] + #[case(sol_token_address(), false)] + #[case(arb_token_address(), false)] + #[case(base_token_address(), false)] + #[case(eth_token_address(), true)] + #[case(sol_token_address(), true)] + #[case(arb_token_address(), true)] + #[case(base_token_address(), true)] + #[tokio::test] + async fn test_token_transfer( + #[case] init_token_address: OmniAddress, + #[case] is_native: bool, + ) -> anyhow::Result<()> { + let env = if is_native { + TestEnv::new_native(init_token_address.get_chain()).await? + } else { + TestEnv::new(init_token_address).await? + }; + let sender = env.create_registered_account(3).await?; + let receiver = env.create_registered_account(4).await?; + let amount = U128(1000000000000000000000000); + + // Mint tokens to sender + fake_finalize_transfer( + &env.locker, + &env.token_contract, + &sender, + env.init_token_address, + amount, + ) + .await?; + + // Transfer tokens + sender + .call(env.token_contract.id(), "ft_transfer") + .args_json(json!({ + "receiver_id": receiver.id(), + "amount": amount, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + // Verify balances + let sender_balance: U128 = env + .token_contract + .view("ft_balance_of") + .args_json(json!({ + "account_id": sender.id(), + })) + .await? + .json()?; + + let receiver_balance: U128 = env + .token_contract + .view("ft_balance_of") + .args_json(json!({ + "account_id": receiver.id(), + })) + .await? + .json()?; + + let total_supply: U128 = env.token_contract.view("ft_total_supply").await?.json()?; + + assert_eq!(sender_balance, U128(0), "Sender balance should be 0"); + assert_eq!( + receiver_balance, amount, + "Receiver balance should be equal to the sent amount" + ); + assert_eq!( + total_supply, amount, + "Total supply should be equal to the minted amount" + ); + + Ok(()) + } + + async fn fake_finalize_transfer( + locker_contract: &near_workspaces::Contract, + token_contract: &near_workspaces::Contract, + recipient: &near_workspaces::Account, + emitter_address: OmniAddress, + amount: U128, + ) -> anyhow::Result<()> { + let storage_deposit_actions = vec![StorageDepositAction { + token_id: token_contract.id().clone(), + account_id: recipient.id().clone(), + storage_deposit_amount: Some(NEP141_DEPOSIT.as_yoctonear()), + }]; + let required_balance_for_fin_transfer: NearToken = locker_contract + .view("required_balance_for_fin_transfer") + .await? + .json()?; + let required_deposit_for_fin_transfer = + NEP141_DEPOSIT.saturating_add(required_balance_for_fin_transfer); + + // Simulate finalization of transfer through locker + locker_contract + .call("fin_transfer") + .args_borsh(FinTransferArgs { + chain_kind: ChainKind::Near, + storage_deposit_actions, + prover_args: borsh::to_vec(&ProverResult::InitTransfer(InitTransferMessage { + origin_nonce: 1, + token: OmniAddress::Near(token_contract.id().clone()), + recipient: OmniAddress::Near(recipient.id().clone()), + amount, + fee: Fee { + fee: U128(0), + native_fee: U128(0), + }, + sender: eth_eoa_address(), + msg: String::default(), + emitter_address, + }))?, + }) + .deposit(required_deposit_for_fin_transfer) + .max_gas() + .transact() + .await? + .into_result()?; + + Ok(()) + } +}