From 645d253d2fe57dbf28cb88915c1ce4f630ac612f Mon Sep 17 00:00:00 2001 From: garikbesson Date: Tue, 10 Sep 2024 13:26:05 +0200 Subject: [PATCH] sandbox tests --- src/deploy.rs | 53 ++++++++----- src/lib.rs | 6 +- tests/sandbox.rs | 197 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 206 insertions(+), 50 deletions(-) diff --git a/src/deploy.rs b/src/deploy.rs index 8411176..6319e47 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -1,30 +1,41 @@ -use near_sdk::{env, json_types::U128, log, near, require, AccountId, NearToken, Promise, PromiseError, PublicKey}; use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata; +use near_sdk::{borsh, env, json_types::U128, near, require, AccountId, NearToken, Promise}; -use crate::{Contract, ContractExt, FT_CONTRACT, NEAR_PER_STORAGE, NO_DEPOSIT, TGAS}; +use crate::{Contract, ContractExt, FT_CONTRACT, NO_DEPOSIT, TGAS}; +type TokenId = String; -#[near(serializers = [json])] +const EXTRA_BYTES: usize = 10000; + +#[near(serializers = [json, borsh])] pub struct TokenArgs { owner_id: AccountId, total_supply: U128, metadata: FungibleTokenMetadata, } +pub fn is_valid_token_id(token_id: &TokenId) -> bool { + for c in token_id.as_bytes() { + match c { + b'0'..=b'9' | b'a'..=b'z' => (), + _ => return false, + } + } + true +} + #[near] impl Contract { - - fn get_required(&self, args: &TokenArgs) -> u128 { - ((FT_WASM_CODE.len() + EXTRA_BYTES + args.try_to_vec().unwrap().len() * 2) as NearToken) - * STORAGE_PRICE_PER_BYTE) - .into() + pub fn get_required(&self, args: &TokenArgs) -> NearToken { + env::storage_byte_cost().saturating_mul( + (FT_CONTRACT.len() + EXTRA_BYTES + borsh::to_vec(args).unwrap().len() * 2) + .try_into() + .unwrap(), + ) } #[payable] - pub fn create_token( - &mut self, - args: TokenArgs, - ) -> Promise { + pub fn create_token(&mut self, args: TokenArgs) -> Promise { args.metadata.assert_valid(); let token_id = args.metadata.symbol.to_ascii_lowercase(); @@ -32,7 +43,7 @@ impl Contract { // Assert the sub-account is valid let token_account_id = format!("{}.{}", token_id, env::current_account_id()); - assert!( + require!( env::is_valid_account_id(token_account_id.as_bytes()), "Token Account ID is invalid" ); @@ -41,23 +52,25 @@ impl Contract { let attached = env::attached_deposit(); let required = self.get_required(&args); - assert!( + require!( attached >= required, - "Attach at least {minimum_needed} yⓃ" + format!("Attach at least {required} yⓃ") ); - let init_args = near_sdk::serde_json::to_vec(args).unwrap(); + let token_account_id: AccountId = format!("{}.{}", token_id, env::current_account_id()) + .parse() + .unwrap(); + let init_args = near_sdk::serde_json::to_vec(&args).unwrap(); - let mut promise = Promise::new(subaccount.clone()) + Promise::new(token_account_id) .create_account() .transfer(attached) - .deploy_contract(FT_CONTRACT) + .deploy_contract(FT_CONTRACT.to_vec()) .function_call( "new".to_owned(), init_args, NO_DEPOSIT, TGAS.saturating_mul(50), - ); + ) } - } diff --git a/src/lib.rs b/src/lib.rs index 4d4ac5f..832e257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,19 @@ // Find all our documentation at https://docs.near.org -use near_sdk::store::LazyOption; use near_sdk::{near, Gas, NearToken}; mod deploy; -const NEAR_PER_STORAGE: NearToken = NearToken::from_yoctonear(10u128.pow(18)); // 10e18yⓃ const FT_CONTRACT: &[u8] = include_bytes!("./ft-contract/ft.wasm"); const TGAS: Gas = Gas::from_tgas(1); // 10e12yⓃ const NO_DEPOSIT: NearToken = NearToken::from_near(0); // 0yⓃ // Define the contract structure #[near(contract_state)] -pub struct Contract { } +pub struct Contract {} // Define the default, which automatically initializes the contract impl Default for Contract { fn default() -> Self { - Self { } + Self {} } } diff --git a/tests/sandbox.rs b/tests/sandbox.rs index a6f89dd..246a57a 100644 --- a/tests/sandbox.rs +++ b/tests/sandbox.rs @@ -1,52 +1,197 @@ -use near_workspaces::types::{AccountId, KeyType, NearToken, SecretKey}; +use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata; +use near_sdk::{json_types::U128, near}; +use near_workspaces::{types::NearToken, Account, AccountId, Contract}; use serde_json::json; +#[near(serializers = [json, borsh])] +struct TokenArgs { + owner_id: AccountId, + total_supply: U128, + metadata: FungibleTokenMetadata, +} + #[tokio::test] async fn main() -> Result<(), Box> { let sandbox = near_workspaces::sandbox().await?; let contract_wasm = near_workspaces::compile_project("./").await?; let contract = sandbox.dev_deploy(&contract_wasm).await?; + let token_owner_account = sandbox.root_account().unwrap(); + let alice_account = token_owner_account + .create_subaccount("alice") + .initial_balance(NearToken::from_near(30)) + .transact() + .await? + .into_result()?; + let bob_account = token_owner_account + .create_subaccount("bob") + .initial_balance(NearToken::from_near(30)) + .transact() + .await? + .into_result()?; + + create_token( + &contract, + &token_owner_account, + &alice_account, + &bob_account, + ) + .await?; - let alice = sandbox - .create_tla( - "alice.test.near".parse().unwrap(), - SecretKey::from_random(KeyType::ED25519), - ) + Ok(()) +} + +async fn create_token( + contract: &Contract, + token_owner_account: &Account, + alice_account: &Account, + bob_account: &Account, +) -> Result<(), Box> { + // Initial setup + let symbol = "TEST"; + let total_supply = U128(100); + let token_id = symbol.to_ascii_lowercase(); + let metadata = FungibleTokenMetadata { + spec: "ft-1.0.0".to_string(), + name: "Test Token".to_string(), + symbol: symbol.to_string(), + decimals: 1, + icon: None, + reference: None, + reference_hash: None, + }; + let token_args = TokenArgs { + owner_id: token_owner_account.id().clone(), + total_supply, + metadata, + }; + + // Getting required deposit based on provided arguments + let required_deposit: serde_json::Value = contract + .view("get_required") + .args_json(json!({"args": token_args})) .await? - .unwrap(); + .json()?; - let bob = sandbox.dev_create_account().await?; + // Creating token with less than required deposit (should fail) + let res_0 = contract + .call("create_token") + .args_json(json!({"args": token_args})) + .max_gas() + .deposit(NearToken::from_yoctonear( + required_deposit.as_str().unwrap().parse::()? - 1, + )) + .transact() + .await?; + assert!(res_0.is_failure()); - let res = contract - .call("create_factory_subaccount_and_deploy") - .args_json(json!({"name": "donation_for_alice", "beneficiary": alice.id()})) + // Creating token with the required deposit + let res_1 = contract + .call("create_token") + .args_json(json!({"args": token_args})) .max_gas() - .deposit(NearToken::from_near(5)) + .deposit(NearToken::from_yoctonear( + required_deposit.as_str().unwrap().parse::()?, + )) .transact() .await?; - assert!(res.is_success()); + assert!(res_1.is_success()); + + // Checking created token account and metadata + let token_account_id: AccountId = format!("{}.{}", token_id, contract.id()).parse().unwrap(); + let token_metadata: FungibleTokenMetadata = token_owner_account + .view(&token_account_id, "ft_metadata") + .args_json(json!({})) + .await? + .json()?; - let sub_accountid: AccountId = format!("donation_for_alice.{}", contract.id()) - .parse() - .unwrap(); + assert_eq!(token_metadata.symbol, symbol); - let res = bob - .view(&sub_accountid, "get_beneficiary") - .args_json({}) - .await?; + // Checking token supply + let token_total_supply: serde_json::Value = token_owner_account + .view(&token_account_id, "ft_total_supply") + .args_json(json!({})) + .await? + .json()?; + assert_eq!( + token_total_supply.as_str().unwrap().parse::()?, + u128::from(total_supply) + ); + + // Checking total supply belongs to the owner account + let token_owner_balance: serde_json::Value = token_owner_account + .view(&token_account_id, "ft_balance_of") + .args_json(json!({"account_id": token_owner_account.id()})) + .await? + .json()?; - assert_eq!(res.json::()?, alice.id().clone()); + assert_eq!( + token_owner_balance.as_str().unwrap().parse::()?, + u128::from(total_supply) + ); - let res = bob - .call(&sub_accountid, "donate") - .args_json({}) + // Checking transfering tokens from owner to other account + let _ = alice_account + .call(&token_account_id, "storage_deposit") + .args_json(json!({"account_id": alice_account.id()})) .max_gas() - .deposit(NearToken::from_near(5)) + .deposit(NearToken::from_millinear(250)) .transact() .await?; + let alice_balance_before: serde_json::Value = alice_account + .view(&token_account_id, "ft_balance_of") + .args_json(json!({"account_id": alice_account.id()})) + .await? + .json()?; + assert_eq!(alice_balance_before, "0"); - assert!(res.is_success()); + let _ = token_owner_account + .call(&token_account_id, "ft_transfer") + .args_json(json!({ + "receiver_id": alice_account.id(), + "amount": "1", + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + + let alice_balance_after: serde_json::Value = alice_account + .view(&token_account_id, "ft_balance_of") + .args_json(json!({"account_id": alice_account.id()})) + .await? + .json()?; + assert_eq!(alice_balance_after, "1"); + // Checking transfering token from alice to bob + let _ = bob_account + .call(&token_account_id, "storage_deposit") + .args_json(json!({"account_id": bob_account.id()})) + .max_gas() + .deposit(NearToken::from_millinear(250)) + .transact() + .await?; + let bob_balance_before: serde_json::Value = bob_account + .view(&token_account_id, "ft_balance_of") + .args_json(json!({"account_id": bob_account.id()})) + .await? + .json()?; + assert_eq!(bob_balance_before, "0"); + let _ = alice_account + .call(&token_account_id, "ft_transfer") + .args_json(json!({ + "receiver_id": bob_account.id(), + "amount": "1", + })) + .max_gas() + .deposit(NearToken::from_yoctonear(1)) + .transact() + .await?; + let bob_balance_after: serde_json::Value = bob_account + .view(&token_account_id, "ft_balance_of") + .args_json(json!({"account_id": bob_account.id()})) + .await? + .json()?; + assert_eq!(bob_balance_after, "1"); Ok(()) }