Skip to content

Commit

Permalink
sandbox tests
Browse files Browse the repository at this point in the history
  • Loading branch information
garikbesson committed Sep 10, 2024
1 parent 629b1b2 commit 645d253
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 50 deletions.
53 changes: 33 additions & 20 deletions src/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
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();

require!(is_valid_token_id(&token_id), "Invalid Symbol");

// 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"
);
Expand All @@ -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),
);
)
}

}
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
197 changes: 171 additions & 26 deletions tests/sandbox.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
// 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::<u128>()? - 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::<u128>()?,
))
.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>()?,
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::<AccountId>()?, alice.id().clone());
assert_eq!(
token_owner_balance.as_str().unwrap().parse::<u128>()?,
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(())
}

0 comments on commit 645d253

Please sign in to comment.