Skip to content

Commit 645d253

Browse files
committed
sandbox tests
1 parent 629b1b2 commit 645d253

File tree

3 files changed

+206
-50
lines changed

3 files changed

+206
-50
lines changed

src/deploy.rs

+33-20
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
1-
use near_sdk::{env, json_types::U128, log, near, require, AccountId, NearToken, Promise, PromiseError, PublicKey};
21
use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata;
2+
use near_sdk::{borsh, env, json_types::U128, near, require, AccountId, NearToken, Promise};
33

4-
use crate::{Contract, ContractExt, FT_CONTRACT, NEAR_PER_STORAGE, NO_DEPOSIT, TGAS};
4+
use crate::{Contract, ContractExt, FT_CONTRACT, NO_DEPOSIT, TGAS};
55

6+
type TokenId = String;
67

7-
#[near(serializers = [json])]
8+
const EXTRA_BYTES: usize = 10000;
9+
10+
#[near(serializers = [json, borsh])]
811
pub struct TokenArgs {
912
owner_id: AccountId,
1013
total_supply: U128,
1114
metadata: FungibleTokenMetadata,
1215
}
1316

17+
pub fn is_valid_token_id(token_id: &TokenId) -> bool {
18+
for c in token_id.as_bytes() {
19+
match c {
20+
b'0'..=b'9' | b'a'..=b'z' => (),
21+
_ => return false,
22+
}
23+
}
24+
true
25+
}
26+
1427
#[near]
1528
impl Contract {
16-
17-
fn get_required(&self, args: &TokenArgs) -> u128 {
18-
((FT_WASM_CODE.len() + EXTRA_BYTES + args.try_to_vec().unwrap().len() * 2) as NearToken)
19-
* STORAGE_PRICE_PER_BYTE)
20-
.into()
29+
pub fn get_required(&self, args: &TokenArgs) -> NearToken {
30+
env::storage_byte_cost().saturating_mul(
31+
(FT_CONTRACT.len() + EXTRA_BYTES + borsh::to_vec(args).unwrap().len() * 2)
32+
.try_into()
33+
.unwrap(),
34+
)
2135
}
2236

2337
#[payable]
24-
pub fn create_token(
25-
&mut self,
26-
args: TokenArgs,
27-
) -> Promise {
38+
pub fn create_token(&mut self, args: TokenArgs) -> Promise {
2839
args.metadata.assert_valid();
2940
let token_id = args.metadata.symbol.to_ascii_lowercase();
3041

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

3344
// Assert the sub-account is valid
3445
let token_account_id = format!("{}.{}", token_id, env::current_account_id());
35-
assert!(
46+
require!(
3647
env::is_valid_account_id(token_account_id.as_bytes()),
3748
"Token Account ID is invalid"
3849
);
@@ -41,23 +52,25 @@ impl Contract {
4152
let attached = env::attached_deposit();
4253
let required = self.get_required(&args);
4354

44-
assert!(
55+
require!(
4556
attached >= required,
46-
"Attach at least {minimum_needed} yⓃ"
57+
format!("Attach at least {required} yⓃ")
4758
);
4859

49-
let init_args = near_sdk::serde_json::to_vec(args).unwrap();
60+
let token_account_id: AccountId = format!("{}.{}", token_id, env::current_account_id())
61+
.parse()
62+
.unwrap();
63+
let init_args = near_sdk::serde_json::to_vec(&args).unwrap();
5064

51-
let mut promise = Promise::new(subaccount.clone())
65+
Promise::new(token_account_id)
5266
.create_account()
5367
.transfer(attached)
54-
.deploy_contract(FT_CONTRACT)
68+
.deploy_contract(FT_CONTRACT.to_vec())
5569
.function_call(
5670
"new".to_owned(),
5771
init_args,
5872
NO_DEPOSIT,
5973
TGAS.saturating_mul(50),
60-
);
74+
)
6175
}
62-
6376
}

src/lib.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
// Find all our documentation at https://docs.near.org
2-
use near_sdk::store::LazyOption;
32
use near_sdk::{near, Gas, NearToken};
43

54
mod deploy;
65

7-
const NEAR_PER_STORAGE: NearToken = NearToken::from_yoctonear(10u128.pow(18)); // 10e18yⓃ
86
const FT_CONTRACT: &[u8] = include_bytes!("./ft-contract/ft.wasm");
97
const TGAS: Gas = Gas::from_tgas(1); // 10e12yⓃ
108
const NO_DEPOSIT: NearToken = NearToken::from_near(0); // 0yⓃ
119

1210
// Define the contract structure
1311
#[near(contract_state)]
14-
pub struct Contract { }
12+
pub struct Contract {}
1513

1614
// Define the default, which automatically initializes the contract
1715
impl Default for Contract {
1816
fn default() -> Self {
19-
Self { }
17+
Self {}
2018
}
2119
}

tests/sandbox.rs

+171-26
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,197 @@
1-
use near_workspaces::types::{AccountId, KeyType, NearToken, SecretKey};
1+
use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata;
2+
use near_sdk::{json_types::U128, near};
3+
use near_workspaces::{types::NearToken, Account, AccountId, Contract};
24
use serde_json::json;
35

6+
#[near(serializers = [json, borsh])]
7+
struct TokenArgs {
8+
owner_id: AccountId,
9+
total_supply: U128,
10+
metadata: FungibleTokenMetadata,
11+
}
12+
413
#[tokio::test]
514
async fn main() -> Result<(), Box<dyn std::error::Error>> {
615
let sandbox = near_workspaces::sandbox().await?;
716
let contract_wasm = near_workspaces::compile_project("./").await?;
817
let contract = sandbox.dev_deploy(&contract_wasm).await?;
18+
let token_owner_account = sandbox.root_account().unwrap();
19+
let alice_account = token_owner_account
20+
.create_subaccount("alice")
21+
.initial_balance(NearToken::from_near(30))
22+
.transact()
23+
.await?
24+
.into_result()?;
25+
let bob_account = token_owner_account
26+
.create_subaccount("bob")
27+
.initial_balance(NearToken::from_near(30))
28+
.transact()
29+
.await?
30+
.into_result()?;
31+
32+
create_token(
33+
&contract,
34+
&token_owner_account,
35+
&alice_account,
36+
&bob_account,
37+
)
38+
.await?;
939

10-
let alice = sandbox
11-
.create_tla(
12-
"alice.test.near".parse().unwrap(),
13-
SecretKey::from_random(KeyType::ED25519),
14-
)
40+
Ok(())
41+
}
42+
43+
async fn create_token(
44+
contract: &Contract,
45+
token_owner_account: &Account,
46+
alice_account: &Account,
47+
bob_account: &Account,
48+
) -> Result<(), Box<dyn std::error::Error>> {
49+
// Initial setup
50+
let symbol = "TEST";
51+
let total_supply = U128(100);
52+
let token_id = symbol.to_ascii_lowercase();
53+
let metadata = FungibleTokenMetadata {
54+
spec: "ft-1.0.0".to_string(),
55+
name: "Test Token".to_string(),
56+
symbol: symbol.to_string(),
57+
decimals: 1,
58+
icon: None,
59+
reference: None,
60+
reference_hash: None,
61+
};
62+
let token_args = TokenArgs {
63+
owner_id: token_owner_account.id().clone(),
64+
total_supply,
65+
metadata,
66+
};
67+
68+
// Getting required deposit based on provided arguments
69+
let required_deposit: serde_json::Value = contract
70+
.view("get_required")
71+
.args_json(json!({"args": token_args}))
1572
.await?
16-
.unwrap();
73+
.json()?;
1774

18-
let bob = sandbox.dev_create_account().await?;
75+
// Creating token with less than required deposit (should fail)
76+
let res_0 = contract
77+
.call("create_token")
78+
.args_json(json!({"args": token_args}))
79+
.max_gas()
80+
.deposit(NearToken::from_yoctonear(
81+
required_deposit.as_str().unwrap().parse::<u128>()? - 1,
82+
))
83+
.transact()
84+
.await?;
85+
assert!(res_0.is_failure());
1986

20-
let res = contract
21-
.call("create_factory_subaccount_and_deploy")
22-
.args_json(json!({"name": "donation_for_alice", "beneficiary": alice.id()}))
87+
// Creating token with the required deposit
88+
let res_1 = contract
89+
.call("create_token")
90+
.args_json(json!({"args": token_args}))
2391
.max_gas()
24-
.deposit(NearToken::from_near(5))
92+
.deposit(NearToken::from_yoctonear(
93+
required_deposit.as_str().unwrap().parse::<u128>()?,
94+
))
2595
.transact()
2696
.await?;
2797

28-
assert!(res.is_success());
98+
assert!(res_1.is_success());
99+
100+
// Checking created token account and metadata
101+
let token_account_id: AccountId = format!("{}.{}", token_id, contract.id()).parse().unwrap();
102+
let token_metadata: FungibleTokenMetadata = token_owner_account
103+
.view(&token_account_id, "ft_metadata")
104+
.args_json(json!({}))
105+
.await?
106+
.json()?;
29107

30-
let sub_accountid: AccountId = format!("donation_for_alice.{}", contract.id())
31-
.parse()
32-
.unwrap();
108+
assert_eq!(token_metadata.symbol, symbol);
33109

34-
let res = bob
35-
.view(&sub_accountid, "get_beneficiary")
36-
.args_json({})
37-
.await?;
110+
// Checking token supply
111+
let token_total_supply: serde_json::Value = token_owner_account
112+
.view(&token_account_id, "ft_total_supply")
113+
.args_json(json!({}))
114+
.await?
115+
.json()?;
116+
assert_eq!(
117+
token_total_supply.as_str().unwrap().parse::<u128>()?,
118+
u128::from(total_supply)
119+
);
120+
121+
// Checking total supply belongs to the owner account
122+
let token_owner_balance: serde_json::Value = token_owner_account
123+
.view(&token_account_id, "ft_balance_of")
124+
.args_json(json!({"account_id": token_owner_account.id()}))
125+
.await?
126+
.json()?;
38127

39-
assert_eq!(res.json::<AccountId>()?, alice.id().clone());
128+
assert_eq!(
129+
token_owner_balance.as_str().unwrap().parse::<u128>()?,
130+
u128::from(total_supply)
131+
);
40132

41-
let res = bob
42-
.call(&sub_accountid, "donate")
43-
.args_json({})
133+
// Checking transfering tokens from owner to other account
134+
let _ = alice_account
135+
.call(&token_account_id, "storage_deposit")
136+
.args_json(json!({"account_id": alice_account.id()}))
44137
.max_gas()
45-
.deposit(NearToken::from_near(5))
138+
.deposit(NearToken::from_millinear(250))
46139
.transact()
47140
.await?;
141+
let alice_balance_before: serde_json::Value = alice_account
142+
.view(&token_account_id, "ft_balance_of")
143+
.args_json(json!({"account_id": alice_account.id()}))
144+
.await?
145+
.json()?;
146+
assert_eq!(alice_balance_before, "0");
48147

49-
assert!(res.is_success());
148+
let _ = token_owner_account
149+
.call(&token_account_id, "ft_transfer")
150+
.args_json(json!({
151+
"receiver_id": alice_account.id(),
152+
"amount": "1",
153+
}))
154+
.max_gas()
155+
.deposit(NearToken::from_yoctonear(1))
156+
.transact()
157+
.await?;
158+
159+
let alice_balance_after: serde_json::Value = alice_account
160+
.view(&token_account_id, "ft_balance_of")
161+
.args_json(json!({"account_id": alice_account.id()}))
162+
.await?
163+
.json()?;
164+
assert_eq!(alice_balance_after, "1");
50165

166+
// Checking transfering token from alice to bob
167+
let _ = bob_account
168+
.call(&token_account_id, "storage_deposit")
169+
.args_json(json!({"account_id": bob_account.id()}))
170+
.max_gas()
171+
.deposit(NearToken::from_millinear(250))
172+
.transact()
173+
.await?;
174+
let bob_balance_before: serde_json::Value = bob_account
175+
.view(&token_account_id, "ft_balance_of")
176+
.args_json(json!({"account_id": bob_account.id()}))
177+
.await?
178+
.json()?;
179+
assert_eq!(bob_balance_before, "0");
180+
let _ = alice_account
181+
.call(&token_account_id, "ft_transfer")
182+
.args_json(json!({
183+
"receiver_id": bob_account.id(),
184+
"amount": "1",
185+
}))
186+
.max_gas()
187+
.deposit(NearToken::from_yoctonear(1))
188+
.transact()
189+
.await?;
190+
let bob_balance_after: serde_json::Value = bob_account
191+
.view(&token_account_id, "ft_balance_of")
192+
.args_json(json!({"account_id": bob_account.id()}))
193+
.await?
194+
.json()?;
195+
assert_eq!(bob_balance_after, "1");
51196
Ok(())
52197
}

0 commit comments

Comments
 (0)