-
Notifications
You must be signed in to change notification settings - Fork 253
/
Copy pathlib.rs
166 lines (148 loc) · 6.85 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*!
Fungible Token implementation with JSON serialization.
NOTES:
- The maximum balance value is limited by U128 (2**128 - 1).
- JSON calls should pass U128 as a base-10 string. E.g. "100".
- The contract optimizes the inner trie structure by hashing account IDs. It will prevent some
abuse of deep tries. Shouldn't be an issue, once NEAR clients implement full hashing of keys.
- The contract tracks the change in storage before and after the call. If the storage increases,
the contract requires the caller of the contract to attach enough deposit to the function call
to cover the storage cost.
This is done to prevent a denial of service attack on the contract by taking all available storage.
If the storage decreases, the contract will issue a refund for the cost of the released storage.
The unused tokens from the attached deposit are also refunded, so it's safe to
attach more deposit than required.
- To prevent the deployed contract from being modified or deleted, it should not have any access
keys on its account.
*/
use near_contract_standards::fungible_token::metadata::{
FungibleTokenMetadata, FungibleTokenMetadataProvider, FT_METADATA_SPEC,
};
use near_contract_standards::fungible_token::FungibleToken;
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::LazyOption;
use near_sdk::json_types::U128;
use near_sdk::{
env, log, near_bindgen, require, AccountId, Balance, BorshStorageKey, PanicOnDefault,
PromiseOrValue,
};
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Contract {
token: FungibleToken,
metadata: LazyOption<FungibleTokenMetadata>,
}
const DATA_IMAGE_SVG_NEAR_ICON: &str = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 288 288'%3E%3Cg id='l' data-name='l'%3E%3Cpath d='M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'/%3E%3C/g%3E%3C/svg%3E";
#[derive(BorshSerialize, BorshStorageKey)]
enum StorageKey {
FungibleToken,
Metadata,
}
#[near_bindgen]
impl Contract {
/// Initializes the contract with the given total supply owned by the given `owner_id` with
/// default metadata (for example purposes only).
#[init]
pub fn new_default_meta(owner_id: AccountId, total_supply: U128) -> Self {
Self::new(
owner_id,
total_supply,
FungibleTokenMetadata {
spec: FT_METADATA_SPEC.to_string(),
name: "Example NEAR fungible token".to_string(),
symbol: "EXAMPLE".to_string(),
icon: Some(DATA_IMAGE_SVG_NEAR_ICON.to_string()),
reference: None,
reference_hash: None,
decimals: 24,
},
)
}
/// Initializes the contract with the given total supply owned by the given `owner_id` with
/// the given fungible token metadata.
#[init]
pub fn new(owner_id: AccountId, total_supply: U128, metadata: FungibleTokenMetadata) -> Self {
require!(!env::state_exists(), "Already initialized");
metadata.assert_valid();
let mut this = Self {
token: FungibleToken::new(StorageKey::FungibleToken),
metadata: LazyOption::new(StorageKey::Metadata, Some(&metadata)),
};
this.token.internal_register_account(&owner_id);
this.token.internal_deposit(&owner_id, total_supply.into());
this
}
fn on_account_closed(&mut self, account_id: AccountId, balance: Balance) {
log!("Closed @{} with {}", account_id, balance);
}
fn on_tokens_burned(&mut self, account_id: AccountId, amount: Balance) {
log!("Account @{} burned {}", account_id, amount);
}
}
near_contract_standards::impl_fungible_token_core!(Contract, token, on_tokens_burned);
near_contract_standards::impl_fungible_token_storage!(Contract, token, on_account_closed);
#[near_bindgen]
impl FungibleTokenMetadataProvider for Contract {
fn ft_metadata(&self) -> FungibleTokenMetadata {
self.metadata.get().unwrap()
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use near_sdk::test_utils::{accounts, VMContextBuilder};
use near_sdk::{testing_env, Balance};
use super::*;
const TOTAL_SUPPLY: Balance = 1_000_000_000_000_000;
fn get_context(predecessor_account_id: AccountId) -> VMContextBuilder {
let mut builder = VMContextBuilder::new();
builder
.current_account_id(accounts(0))
.signer_account_id(predecessor_account_id.clone())
.predecessor_account_id(predecessor_account_id);
builder
}
#[test]
fn test_new() {
let mut context = get_context(accounts(1));
testing_env!(context.build());
let contract = Contract::new_default_meta(accounts(1).into(), TOTAL_SUPPLY.into());
testing_env!(context.is_view(true).build());
assert_eq!(contract.ft_total_supply().0, TOTAL_SUPPLY);
assert_eq!(contract.ft_balance_of(accounts(1)).0, TOTAL_SUPPLY);
}
#[test]
#[should_panic(expected = "The contract is not initialized")]
fn test_default() {
let context = get_context(accounts(1));
testing_env!(context.build());
let _contract = Contract::default();
}
#[test]
fn test_transfer() {
let mut context = get_context(accounts(2));
testing_env!(context.build());
let mut contract = Contract::new_default_meta(accounts(2).into(), TOTAL_SUPPLY.into());
testing_env!(context
.storage_usage(env::storage_usage())
.attached_deposit(contract.storage_balance_bounds().min.into())
.predecessor_account_id(accounts(1))
.build());
// Paying for account registration, aka storage deposit
contract.storage_deposit(None, None);
testing_env!(context
.storage_usage(env::storage_usage())
.attached_deposit(1)
.predecessor_account_id(accounts(2))
.build());
let transfer_amount = TOTAL_SUPPLY / 3;
contract.ft_transfer(accounts(1), transfer_amount.into(), None);
testing_env!(context
.storage_usage(env::storage_usage())
.account_balance(env::account_balance())
.is_view(true)
.attached_deposit(0)
.build());
assert_eq!(contract.ft_balance_of(accounts(2)).0, (TOTAL_SUPPLY - transfer_amount));
assert_eq!(contract.ft_balance_of(accounts(1)).0, transfer_amount);
}
}