├── .gitignore
├── Dockerfile
├── LICENSE
├── add_sale.sh
├── build_all.sh
├── common
├── fungible_token.wasm
└── w_near.wasm
├── docker_build.sh
├── init_sales.sh
├── lockup
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── README.md
├── build.sh
├── src
│ └── lib.rs
└── tests
│ └── mod.rs
├── lockup_csv_to_borsh
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── README.md
├── example.csv
├── run.sh
├── run_example.sh
└── src
│ └── main.rs
├── permissions
├── Cargo.lock
├── Cargo.toml
├── build.sh
└── src
│ └── lib.rs
├── release
├── lockup0.wasm
├── lockup1.wasm
├── lockup2.wasm
├── lockup3.wasm
└── skyward.wasm
├── setup.sh
├── skyward
├── Cargo.lock
├── Cargo.toml
├── README.md
├── build.sh
├── src
│ ├── account.rs
│ ├── errors.rs
│ ├── internal.rs
│ ├── lib.rs
│ ├── sale.rs
│ ├── sub.rs
│ ├── treasury.rs
│ └── utils.rs
└── tests
│ └── mod.rs
├── test_all.sh
└── token_swap_testnet
├── Cargo.lock
├── Cargo.toml
├── build.sh
└── src
└── lib.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
3 | neardev/
4 | res/
5 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rust:1.51.0
2 |
3 | LABEL description="Container for builds"
4 |
5 | RUN rustup default 1.51.0
6 | RUN rustup target add wasm32-unknown-unknown
7 |
8 | RUN apt-get update && apt-get install -y git less vim
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
26 |
--------------------------------------------------------------------------------
/add_sale.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cd "$(dirname $0)"
5 |
6 | [ "$#" -eq 5 ] || die "Skyward account ID, Owner Id, Token Id Want, Token Id Sold, Sold amount argument required, $# provided"
7 |
8 | ACCOUNT_ID=$1
9 |
10 | CONTRACT_ID=$ACCOUNT_ID
11 |
12 | OWNER_ID=$2
13 | TOKEN_ACCOUNT_ID_IN=$3
14 | TOKEN_ACCOUNT_ID_OUT=$4
15 | AMOUNT=$5
16 |
17 | near call $TOKEN_ACCOUNT_ID_OUT --accountId=$OWNER_ID storage_deposit '{"account_id": "'$CONTRACT_ID'"}' --amount=0.00125
18 | near call $CONTRACT_ID --accountId=$OWNER_ID register_token '{"token_account_id": "'$TOKEN_ACCOUNT_ID_OUT'"}' --amount=0.01
19 | near call $TOKEN_ACCOUNT_ID_OUT --accountId=$OWNER_ID ft_transfer_call '{"receiver_id": "'$CONTRACT_ID'", "amount": "'$AMOUNT'", "msg":"\"AccountDeposit\""}' --amount=0.000000000000000000000001
20 |
21 | TIME=$(date +%s)
22 | START_TIME=$(expr $TIME + 604805)
23 | near call $CONTRACT_ID --accountId=$OWNER_ID sale_create '{"sale": {
24 | "title": "[TESTNET] Custom token sale",
25 | "out_tokens": [{
26 | "token_account_id": "'$TOKEN_ACCOUNT_ID_OUT'",
27 | "balance": "'$AMOUNT'",
28 | "referral_bpt": 100
29 | }],
30 | "in_token_account_id": "'$TOKEN_ACCOUNT_ID_IN'",
31 | "start_time": "'$START_TIME'000000000",
32 | "duration": "604800000000000"
33 | }}' --amount=11
34 |
--------------------------------------------------------------------------------
/build_all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | skyward/build.sh
6 | lockup_csv_to_borsh/run_example.sh
7 | lockup/build.sh
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/common/fungible_token.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/common/fungible_token.wasm
--------------------------------------------------------------------------------
/common/w_near.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/common/w_near.wasm
--------------------------------------------------------------------------------
/docker_build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 | HOST_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
5 |
6 | echo "Building the docker image"
7 | docker build -t contract-builder .
8 |
9 | echo "Building the skyward contract"
10 | docker run \
11 | --mount type=bind,source=$HOST_DIR,target=/host \
12 | --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
13 | -i -t contract-builder \
14 | host/skyward/build.sh
15 |
16 | echo "Comparing to the release"
17 | cmp --silent release/skyward.wasm skyward/res/skyward.wasm || echo "files are different"
18 |
19 |
--------------------------------------------------------------------------------
/init_sales.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd "$(dirname $0)"
4 |
5 | [ "$#" -eq 1 ] || die "One Account ID argument required, $# provided"
6 |
7 | ACCOUNT_ID=$1
8 | SKYWARD_TOKEN_ID=token.$ACCOUNT_ID
9 | CONTRACT_ID=$ACCOUNT_ID
10 | WRAP_NEAR_TOKEN_ID=wrap.near
11 |
12 | START_TIME=1625097600
13 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
14 | "title": "SKYWARD 25% Initial sale",
15 | "out_tokens": [{
16 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
17 | "balance": "250000000000000000000000",
18 | "referral_bpt": 100
19 | }],
20 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
21 | "start_time": "'$START_TIME'000000000",
22 | "duration": "604800000000000"
23 | }}'
24 |
25 | START_TIME=1627776000
26 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
27 | "title": "SKYWARD 20% August sale",
28 | "out_tokens": [{
29 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
30 | "balance": "200000000000000000000000",
31 | "referral_bpt": 100
32 | }],
33 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
34 | "start_time": "'$START_TIME'000000000",
35 | "duration": "604800000000000"
36 | }}'
37 |
38 | START_TIME=1630454400
39 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
40 | "title": "SKYWARD 15% September sale",
41 | "out_tokens": [{
42 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
43 | "balance": "150000000000000000000000",
44 | "referral_bpt": 100
45 | }],
46 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
47 | "start_time": "'$START_TIME'000000000",
48 | "duration": "604800000000000"
49 | }}'
50 |
51 | START_TIME=1633046400
52 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
53 | "title": "SKYWARD 10% October sale",
54 | "out_tokens": [{
55 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
56 | "balance": "100000000000000000000000",
57 | "referral_bpt": 100
58 | }],
59 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
60 | "start_time": "'$START_TIME'000000000",
61 | "duration": "604800000000000"
62 | }}'
63 |
64 | START_TIME=1635724800
65 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
66 | "title": "SKYWARD 10% November sale",
67 | "out_tokens": [{
68 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
69 | "balance": "100000000000000000000000",
70 | "referral_bpt": 100
71 | }],
72 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
73 | "start_time": "'$START_TIME'000000000",
74 | "duration": "604800000000000"
75 | }}'
76 |
77 | START_TIME=1638316800
78 | near call $CONTRACT_ID --accountId=$CONTRACT_ID sale_create '{"sale": {
79 | "title": "SKYWARD 10% Final sale",
80 | "out_tokens": [{
81 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
82 | "balance": "100000000000000000000000",
83 | "referral_bpt": 100
84 | }],
85 | "in_token_account_id": "'$WRAP_NEAR_TOKEN_ID'",
86 | "start_time": "'$START_TIME'000000000",
87 | "duration": "604800000000000"
88 | }}'
89 |
--------------------------------------------------------------------------------
/lockup/.gitignore:
--------------------------------------------------------------------------------
1 | data/
2 |
--------------------------------------------------------------------------------
/lockup/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lockup"
3 | version = "0.2.0"
4 | authors = ["Spensa Nightshade "]
5 | edition = "2018"
6 |
7 | [lib]
8 | crate-type = ["cdylib", "rlib"]
9 |
10 | [dependencies]
11 | near-sdk = "3.1.0"
12 | near-contract-standards = "3.1.0"
13 | uint = { version = "0.9.0", default-features = false }
14 |
15 | [dev-dependencies]
16 | near-sdk-sim = "3.2.0"
17 |
18 | [profile.release]
19 | codegen-units=1
20 | opt-level = "z"
21 | lto = true
22 | debug = false
23 | panic = "abort"
24 | overflow-checks = true
25 |
--------------------------------------------------------------------------------
/lockup/README.md:
--------------------------------------------------------------------------------
1 | # Fungible Token Lockup contract
2 |
3 | Make sure you update `data/accounts.borsh` before building the contract.
4 |
--------------------------------------------------------------------------------
/lockup/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | for f in data/lockup*.borsh
6 | do
7 | echo $f
8 | cp $f data/accounts.borsh
9 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
10 | mkdir -p ./res
11 | cp target/wasm32-unknown-unknown/release/lockup.wasm ./res/$(basename $f .borsh).wasm
12 | done
13 |
14 | popd
15 |
--------------------------------------------------------------------------------
/lockup/src/lib.rs:
--------------------------------------------------------------------------------
1 | use near_contract_standards::fungible_token::core_impl::ext_fungible_token;
2 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
3 | use near_sdk::collections::LookupMap;
4 | use near_sdk::json_types::{ValidAccountId, WrappedBalance};
5 | use near_sdk::serde::{Deserialize, Serialize};
6 | use near_sdk::{
7 | env, ext_contract, is_promise_success, log, near_bindgen, AccountId, Balance, BorshStorageKey,
8 | CryptoHash, Gas, PanicOnDefault, Promise, PromiseOrValue, Timestamp,
9 | };
10 | use std::cmp::Ordering;
11 |
12 | near_sdk::setup_alloc!();
13 |
14 | #[derive(BorshStorageKey, BorshSerialize)]
15 | pub(crate) enum StorageKey {
16 | Accounts,
17 | }
18 |
19 | pub type TimestampSec = u32;
20 | pub type TokenAccountId = AccountId;
21 |
22 | const CRYPTO_HASH_SIZE: usize = 32;
23 | const GAS_FOR_FT_TRANSFER: Gas = 10_000_000_000_000;
24 | const GAS_FOR_AFTER_FT_TRANSFER: Gas = 10_000_000_000_000;
25 | const GAS_FOR_FT_TRANSFER_CALL: Gas = 50_000_000_000_000;
26 | const LOCKUP_DATA: &[u8] = include_bytes!("../data/accounts.borsh");
27 | const SIZE_OF_FIXED_SIZE_ACCOUNT: usize = 60;
28 | const BALANCE_OFFSET: usize = 44;
29 | const NUM_LOCKUP_ACCOUNTS: usize = LOCKUP_DATA.len() / SIZE_OF_FIXED_SIZE_ACCOUNT;
30 |
31 | const MAX_STORAGE_PER_ACCOUNT: u64 = 121;
32 | const SELF_STORAGE: u64 = 1000;
33 |
34 | const ONE_YOCTO: Balance = 1;
35 | const NO_DEPOSIT: Balance = 0;
36 |
37 | uint::construct_uint! {
38 | pub struct U256(4);
39 | }
40 |
41 | #[ext_contract(ext_self)]
42 | trait SelfCallbacks {
43 | fn after_ft_transfer(&mut self, account_id: AccountId, amount: WrappedBalance) -> bool;
44 | }
45 |
46 | trait SelfCallbacks {
47 | fn after_ft_transfer(&mut self, account_id: AccountId, amount: WrappedBalance) -> bool;
48 | }
49 |
50 | #[derive(BorshDeserialize)]
51 | pub struct FixedSizeAccount {
52 | pub account_hash: CryptoHash,
53 | pub start_timestamp: TimestampSec,
54 | pub cliff_timestamp: TimestampSec,
55 | pub end_timestamp: TimestampSec,
56 | pub balance: u128,
57 | }
58 |
59 | #[derive(BorshDeserialize, BorshSerialize)]
60 | pub struct Account {
61 | pub index: u32,
62 | pub claimed_balance: Balance,
63 | }
64 |
65 | #[derive(Serialize, Deserialize)]
66 | #[serde(crate = "near_sdk::serde")]
67 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
68 | pub struct AccountOutput {
69 | pub start_timestamp: TimestampSec,
70 | pub cliff_timestamp: TimestampSec,
71 | pub end_timestamp: TimestampSec,
72 | pub balance: WrappedBalance,
73 | pub claimed_balance: WrappedBalance,
74 | }
75 |
76 | #[near_bindgen]
77 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
78 | pub struct Contract {
79 | pub accounts: LookupMap,
80 |
81 | pub token_account_id: TokenAccountId,
82 |
83 | pub skyward_account_id: AccountId,
84 |
85 | pub claim_expiration_timestamp: TimestampSec,
86 |
87 | pub total_balance: Balance,
88 |
89 | pub untouched_balance: Balance,
90 |
91 | pub total_claimed: Balance,
92 | }
93 |
94 | #[derive(Serialize, Deserialize)]
95 | #[serde(crate = "near_sdk::serde")]
96 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
97 | pub struct Stats {
98 | pub token_account_id: TokenAccountId,
99 |
100 | pub skyward_account_id: AccountId,
101 |
102 | pub claim_expiration_timestamp: TimestampSec,
103 |
104 | pub total_balance: WrappedBalance,
105 |
106 | pub untouched_balance: WrappedBalance,
107 |
108 | pub total_claimed: WrappedBalance,
109 | }
110 |
111 | #[near_bindgen]
112 | impl Contract {
113 | #[init]
114 | pub fn new(
115 | token_account_id: ValidAccountId,
116 | skyward_account_id: ValidAccountId,
117 | claim_expiration_timestamp: TimestampSec,
118 | ) -> Self {
119 | let total_balance = compute_total_balance();
120 | let required_storage_cost = Balance::from(
121 | env::storage_usage()
122 | + SELF_STORAGE
123 | + MAX_STORAGE_PER_ACCOUNT * (NUM_LOCKUP_ACCOUNTS as u64),
124 | ) * env::storage_byte_cost();
125 | assert!(env::account_balance() >= required_storage_cost);
126 | Self {
127 | accounts: LookupMap::new(StorageKey::Accounts),
128 | token_account_id: token_account_id.into(),
129 | skyward_account_id: skyward_account_id.into(),
130 | claim_expiration_timestamp,
131 | total_balance,
132 | untouched_balance: total_balance,
133 | total_claimed: 0,
134 | }
135 | }
136 |
137 | pub fn get_account(&self, account_id: ValidAccountId) -> Option {
138 | self.internal_get_account(account_id.as_ref())
139 | .map(|(account, fixed_size_account)| AccountOutput {
140 | start_timestamp: fixed_size_account.start_timestamp,
141 | cliff_timestamp: fixed_size_account.cliff_timestamp,
142 | end_timestamp: fixed_size_account.end_timestamp,
143 | balance: fixed_size_account.balance.into(),
144 | claimed_balance: account.claimed_balance.into(),
145 | })
146 | }
147 |
148 | pub fn claim(&mut self) -> PromiseOrValue {
149 | let account_id = env::predecessor_account_id();
150 | let (
151 | mut account,
152 | FixedSizeAccount {
153 | start_timestamp,
154 | cliff_timestamp,
155 | end_timestamp,
156 | balance,
157 | ..
158 | },
159 | ) = self
160 | .internal_get_account(&account_id)
161 | .expect("The claim is not found");
162 | let current_timestamp = env::block_timestamp();
163 | let unlocked_balance: Balance = if current_timestamp < to_nano(cliff_timestamp) {
164 | 0
165 | } else if current_timestamp >= to_nano(end_timestamp) {
166 | balance
167 | } else {
168 | let total_duration = to_nano(end_timestamp - start_timestamp);
169 | let passed_duration = current_timestamp - to_nano(start_timestamp);
170 | (U256::from(passed_duration) * U256::from(balance) / U256::from(total_duration))
171 | .as_u128()
172 | };
173 | let claim_balance = unlocked_balance - account.claimed_balance;
174 | account.claimed_balance = unlocked_balance;
175 | self.total_claimed += claim_balance;
176 | if self.accounts.insert(&account_id, &account).is_none() {
177 | // New claim, have to remove this from untouched balance.
178 | self.untouched_balance -= balance;
179 | }
180 | if claim_balance > 0 {
181 | ext_fungible_token::ft_transfer(
182 | account_id.clone(),
183 | claim_balance.into(),
184 | Some(format!(
185 | "Claiming unlocked {} balance from {}",
186 | claim_balance,
187 | env::current_account_id()
188 | )),
189 | &self.token_account_id,
190 | ONE_YOCTO,
191 | GAS_FOR_FT_TRANSFER,
192 | )
193 | .then(ext_self::after_ft_transfer(
194 | account_id,
195 | claim_balance.into(),
196 | &env::current_account_id(),
197 | NO_DEPOSIT,
198 | GAS_FOR_AFTER_FT_TRANSFER,
199 | ))
200 | .into()
201 | } else {
202 | PromiseOrValue::Value(true)
203 | }
204 | }
205 |
206 | pub fn get_stats(&self) -> Stats {
207 | Stats {
208 | token_account_id: self.token_account_id.clone(),
209 | skyward_account_id: self.skyward_account_id.clone(),
210 | claim_expiration_timestamp: self.claim_expiration_timestamp,
211 | total_balance: self.total_balance.into(),
212 | untouched_balance: self.untouched_balance.into(),
213 | total_claimed: self.total_claimed.into(),
214 | }
215 | }
216 |
217 | pub fn donate_to_treasury(&mut self) -> Promise {
218 | assert!(
219 | env::block_timestamp() >= to_nano(self.claim_expiration_timestamp),
220 | "The claims are not expired yet"
221 | );
222 | if self.untouched_balance > 0 {
223 | let message = format!(
224 | "Donating remaining {} untouched token balance from {} to Skyward treasury",
225 | self.untouched_balance,
226 | env::current_account_id()
227 | );
228 | log!(message);
229 | ext_fungible_token::ft_transfer_call(
230 | self.skyward_account_id.clone(),
231 | self.untouched_balance.into(),
232 | Some(message),
233 | "\"DonateToTreasury\"".to_string(),
234 | &self.token_account_id,
235 | ONE_YOCTO,
236 | GAS_FOR_FT_TRANSFER_CALL,
237 | );
238 | self.total_balance -= self.untouched_balance;
239 | self.untouched_balance = 0;
240 | }
241 | let unused_near_balance =
242 | env::account_balance() - Balance::from(env::storage_usage()) * env::storage_byte_cost();
243 | log!("Donating {} NEAR to Skyward", unused_near_balance);
244 | Promise::new(self.skyward_account_id.clone()).transfer(unused_near_balance)
245 | }
246 |
247 | fn internal_get_account(&self, account_id: &AccountId) -> Option<(Account, FixedSizeAccount)> {
248 | self.accounts
249 | .get(account_id)
250 | .map(|account| {
251 | let fixed_size_account = get_fixed_size_account(account.index as usize);
252 | (account, fixed_size_account)
253 | })
254 | .or_else(|| {
255 | if env::block_timestamp() < to_nano(self.claim_expiration_timestamp) {
256 | if let Some(index) = find_account(&account_id) {
257 | return Some((
258 | Account {
259 | index: index as u32,
260 | claimed_balance: 0,
261 | },
262 | get_fixed_size_account(index),
263 | ));
264 | }
265 | }
266 | None
267 | })
268 | }
269 | }
270 |
271 | #[near_bindgen]
272 | impl SelfCallbacks for Contract {
273 | #[private]
274 | fn after_ft_transfer(&mut self, account_id: AccountId, amount: WrappedBalance) -> bool {
275 | let promise_success = is_promise_success();
276 | if !promise_success {
277 | let mut account = self
278 | .accounts
279 | .get(&account_id)
280 | .expect("The claim is not found");
281 | account.claimed_balance -= amount.0;
282 | self.total_claimed -= amount.0;
283 | self.accounts.insert(&account_id, &account);
284 | }
285 | promise_success
286 | }
287 | }
288 |
289 | fn hash_account(account_id: &AccountId) -> CryptoHash {
290 | let value_hash = env::sha256(account_id.as_bytes());
291 | let mut res = CryptoHash::default();
292 | res.copy_from_slice(&value_hash);
293 |
294 | res
295 | }
296 |
297 | fn find_account(expected_account_id: &AccountId) -> Option {
298 | let expected_account_hash = hash_account(expected_account_id);
299 | // Less or equal to expected_account_hash (inclusive)
300 | let mut left = 0;
301 | // Strictly greater than expected_account_hash
302 | let mut right = NUM_LOCKUP_ACCOUNTS;
303 | while left < right {
304 | let mid = (left + right) / 2;
305 | let account_hash = get_account_hash_at(mid);
306 | match expected_account_hash.cmp(&account_hash) {
307 | Ordering::Less => right = mid,
308 | Ordering::Equal => return Some(mid),
309 | Ordering::Greater => left = mid + 1,
310 | }
311 | }
312 | None
313 | }
314 |
315 | fn get_account_hash_at(index: usize) -> CryptoHash {
316 | let offset = index * SIZE_OF_FIXED_SIZE_ACCOUNT;
317 | let mut res = CryptoHash::default();
318 | res.copy_from_slice(&LOCKUP_DATA[offset..offset + CRYPTO_HASH_SIZE]);
319 | res
320 | }
321 |
322 | fn get_fixed_size_account(index: usize) -> FixedSizeAccount {
323 | FixedSizeAccount::try_from_slice(
324 | &LOCKUP_DATA
325 | [(index * SIZE_OF_FIXED_SIZE_ACCOUNT)..((index + 1) * SIZE_OF_FIXED_SIZE_ACCOUNT)],
326 | )
327 | .unwrap()
328 | }
329 |
330 | fn get_fixed_size_account_balance(index: usize) -> Balance {
331 | Balance::try_from_slice(
332 | &LOCKUP_DATA[(index * SIZE_OF_FIXED_SIZE_ACCOUNT + BALANCE_OFFSET)
333 | ..((index + 1) * SIZE_OF_FIXED_SIZE_ACCOUNT)],
334 | )
335 | .unwrap()
336 | }
337 |
338 | fn to_nano(timestamp: TimestampSec) -> Timestamp {
339 | Timestamp::from(timestamp) * 10u64.pow(9)
340 | }
341 |
342 | fn compute_total_balance() -> Balance {
343 | (0..NUM_LOCKUP_ACCOUNTS)
344 | .map(|index| get_fixed_size_account_balance(index))
345 | .sum()
346 | }
347 |
--------------------------------------------------------------------------------
/lockup/tests/mod.rs:
--------------------------------------------------------------------------------
1 | use lockup::{AccountOutput, ContractContract as LockupContract, Stats, TimestampSec};
2 | use near_contract_standards::fungible_token::metadata::{FungibleTokenMetadata, FT_METADATA_SPEC};
3 | use near_sdk::env::STORAGE_PRICE_PER_BYTE;
4 | use near_sdk::json_types::{ValidAccountId, WrappedBalance, U128};
5 | use near_sdk::serde::Serialize;
6 | use near_sdk::serde_json::json;
7 | use near_sdk::{env, Balance, Gas, Timestamp};
8 | use near_sdk_sim::runtime::GenesisConfig;
9 | use near_sdk_sim::{
10 | deploy, init_simulator, to_yocto, ContractAccount, ExecutionResult, UserAccount,
11 | };
12 | use std::convert::TryInto;
13 |
14 | near_sdk_sim::lazy_static_include::lazy_static_include_bytes! {
15 | LOCKUP_WASM_BYTES => "res/lockup0.wasm",
16 | SKYWARD_WASM_BYTES => "../skyward/res/skyward.wasm",
17 |
18 | FUNGIBLE_TOKEN_WASM_BYTES => "../common/fungible_token.wasm",
19 | }
20 |
21 | pub fn accounts(id: usize) -> ValidAccountId {
22 | ["alice.near", "bob.near", "charlie.near"][id]
23 | .to_string()
24 | .try_into()
25 | .unwrap()
26 | }
27 |
28 | fn to_nano(timestamp: TimestampSec) -> Timestamp {
29 | Timestamp::from(timestamp) * 10u64.pow(9)
30 | }
31 |
32 | const NEAR: &str = "near";
33 | const SKYWARD_ID: &str = "skyward.near";
34 | const SKYWARD_CLAIM_ID: &str = "claim.skyward.near";
35 | const SKYWARD_TOKEN_ID: &str = "token.skyward.near";
36 | const WRAP_NEAR_ID: &str = "wrap_near.skyward.near";
37 | const SKYWARD_DAO_ID: &str = "skyward-dao.near";
38 |
39 | const BASE_GAS: Gas = 10_000_000_000_000;
40 | const CLAIM_GAS: Gas = 60_000_000_000_000;
41 | const DONATE_GAS: Gas = 80_000_000_000_000;
42 | const SKYWARD_TOKEN_DECIMALS: u8 = 18;
43 | const SKYWARD_TOKEN_BASE: Balance = 10u128.pow(SKYWARD_TOKEN_DECIMALS as u32);
44 | const SKYWARD_TOTAL_SUPPLY: Balance = 1_000_000 * SKYWARD_TOKEN_BASE;
45 | const ONE_NEAR: Balance = 10u128.pow(24);
46 | const LISTING_FEE_NEAR: Balance = 10 * ONE_NEAR;
47 |
48 | // From example.csv
49 | const CLAIM_EXPIRATION_TIMESTAMP: TimestampSec = 1640000000;
50 | const TOTAL_LOCKUP_BALANCE: Balance = 10010000000000000000;
51 | const TIMESTAMP_1: TimestampSec = 1622505600;
52 | const TIMESTAMP_2: TimestampSec = 1625097600;
53 | const TIMESTAMP_3: TimestampSec = 1633046400;
54 | const BALANCE_1: Balance = 10u128.pow(16);
55 | const BALANCE_2: Balance = 10u128.pow(19);
56 |
57 | #[derive(Serialize)]
58 | #[serde(crate = "near_sdk::serde")]
59 | struct VestingIntervalInput {
60 | pub start_timestamp: TimestampSec,
61 | pub end_timestamp: TimestampSec,
62 | pub amount: WrappedBalance,
63 | }
64 |
65 | pub struct Env {
66 | pub root: UserAccount,
67 | pub near: UserAccount,
68 | pub skyward_claim: ContractAccount,
69 | pub skyward_dao: UserAccount,
70 | pub skyward: UserAccount,
71 | pub skyward_token: UserAccount,
72 |
73 | pub users: Vec,
74 | }
75 |
76 | fn storage_deposit(user: &UserAccount, token_account_id: &str, account_id: &str) {
77 | user.call(
78 | token_account_id.to_string(),
79 | "storage_deposit",
80 | &json!({
81 | "account_id": account_id.to_string()
82 | })
83 | .to_string()
84 | .into_bytes(),
85 | BASE_GAS,
86 | 125 * env::STORAGE_PRICE_PER_BYTE, // attached deposit
87 | )
88 | .assert_success();
89 | }
90 |
91 | impl Env {
92 | pub fn init(num_users: usize) -> Self {
93 | let mut genesis_config = GenesisConfig::default();
94 | genesis_config.runtime_config.storage_amount_per_byte = STORAGE_PRICE_PER_BYTE;
95 | let root = init_simulator(Some(genesis_config));
96 | let near = root.create_user(NEAR.to_string(), to_yocto("1000"));
97 | let skyward_dao = near.create_user(SKYWARD_DAO_ID.to_string(), to_yocto("100"));
98 | let skyward = near.deploy_and_init(
99 | &SKYWARD_WASM_BYTES,
100 | SKYWARD_ID.to_string(),
101 | "new",
102 | &json!({
103 | "skyward_token_id": SKYWARD_TOKEN_ID.to_string(),
104 | "skyward_vesting_schedule": vec![
105 | VestingIntervalInput {
106 | start_timestamp: 0,
107 | end_timestamp: 1,
108 | amount: U128(SKYWARD_TOTAL_SUPPLY),
109 | },
110 | ],
111 | "listing_fee_near": U128::from(LISTING_FEE_NEAR),
112 | "w_near_token_id": WRAP_NEAR_ID.to_string(),
113 | })
114 | .to_string()
115 | .into_bytes(),
116 | to_yocto("30"),
117 | BASE_GAS,
118 | );
119 | let skyward_token = skyward.deploy_and_init(
120 | &FUNGIBLE_TOKEN_WASM_BYTES,
121 | SKYWARD_TOKEN_ID.to_string(),
122 | "new",
123 | &json!({
124 | "owner_id": skyward_dao.valid_account_id(),
125 | "total_supply": U128::from(SKYWARD_TOTAL_SUPPLY),
126 | "metadata": FungibleTokenMetadata {
127 | spec: FT_METADATA_SPEC.to_string(),
128 | name: "Skyward Finance Token".to_string(),
129 | symbol: "SKYWARD".to_string(),
130 | icon: None,
131 | reference: None,
132 | reference_hash: None,
133 | decimals: SKYWARD_TOKEN_DECIMALS,
134 | }
135 | })
136 | .to_string()
137 | .into_bytes(),
138 | to_yocto("10"),
139 | BASE_GAS,
140 | );
141 | let skyward_claim = deploy!(
142 | contract: LockupContract,
143 | contract_id: SKYWARD_CLAIM_ID.to_string(),
144 | bytes: &LOCKUP_WASM_BYTES,
145 | signer_account: skyward,
146 | deposit: to_yocto("10"),
147 | gas: BASE_GAS,
148 | init_method: new(skyward_token.valid_account_id(), skyward.valid_account_id(), CLAIM_EXPIRATION_TIMESTAMP)
149 | );
150 | // Registering tokens
151 | storage_deposit(&skyward_dao, SKYWARD_TOKEN_ID, SKYWARD_ID);
152 | storage_deposit(&skyward_dao, SKYWARD_TOKEN_ID, SKYWARD_CLAIM_ID);
153 |
154 | // Give total lockup balance to claim.
155 | skyward_dao
156 | .call(
157 | SKYWARD_TOKEN_ID.to_string(),
158 | "ft_transfer",
159 | &json!({
160 | "receiver_id": SKYWARD_CLAIM_ID.to_string(),
161 | "amount": U128::from(TOTAL_LOCKUP_BALANCE),
162 | })
163 | .to_string()
164 | .into_bytes(),
165 | BASE_GAS,
166 | 1,
167 | )
168 | .assert_success();
169 |
170 | let mut this = Self {
171 | root,
172 | near,
173 | skyward_claim,
174 | skyward_dao,
175 | skyward,
176 | skyward_token,
177 | users: vec![],
178 | };
179 | this.init_users(num_users);
180 | this
181 | }
182 |
183 | pub fn init_users(&mut self, num_users: usize) {
184 | for i in 0..num_users {
185 | let user = self.near.create_user(accounts(i).into(), to_yocto("100"));
186 | storage_deposit(&user, SKYWARD_TOKEN_ID, &user.account_id);
187 | self.users.push(user);
188 | }
189 | }
190 |
191 | pub fn get_account(&self, user: &UserAccount) -> Option {
192 | self.near
193 | .view_method_call(
194 | self.skyward_claim
195 | .contract
196 | .get_account(user.valid_account_id()),
197 | )
198 | .unwrap_json()
199 | }
200 |
201 | pub fn get_stats(&self) -> Stats {
202 | self.near
203 | .view_method_call(self.skyward_claim.contract.get_stats())
204 | .unwrap_json()
205 | }
206 |
207 | pub fn claim(&self, user: &UserAccount) -> ExecutionResult {
208 | user.function_call(self.skyward_claim.contract.claim(), CLAIM_GAS, 0)
209 | }
210 |
211 | pub fn get_skyward_token_balance(&self, user: &UserAccount) -> Balance {
212 | let balance: Option = self
213 | .near
214 | .view(
215 | SKYWARD_TOKEN_ID.to_string(),
216 | "ft_balance_of",
217 | &json!({
218 | "account_id": user.valid_account_id(),
219 | })
220 | .to_string()
221 | .into_bytes(),
222 | )
223 | .unwrap_json();
224 | balance.unwrap().0
225 | }
226 |
227 | pub fn get_treasury_circulating_supply(&self) -> Balance {
228 | let balance: WrappedBalance = self
229 | .near
230 | .view(
231 | SKYWARD_ID.to_string(),
232 | "get_skyward_circulating_supply",
233 | b"{}",
234 | )
235 | .unwrap_json();
236 | balance.0
237 | }
238 | }
239 |
240 | #[test]
241 | fn test_init() {
242 | Env::init(0);
243 | }
244 |
245 | #[test]
246 | fn test_initial_get_account() {
247 | let e = Env::init(3);
248 | assert_eq!(
249 | e.get_account(&e.users[0]),
250 | Some(AccountOutput {
251 | start_timestamp: TIMESTAMP_1,
252 | cliff_timestamp: TIMESTAMP_1,
253 | end_timestamp: TIMESTAMP_2,
254 | balance: U128(BALANCE_1),
255 | claimed_balance: U128(0)
256 | })
257 | );
258 | assert_eq!(
259 | e.get_account(&e.users[1]),
260 | Some(AccountOutput {
261 | start_timestamp: TIMESTAMP_1,
262 | cliff_timestamp: TIMESTAMP_2,
263 | end_timestamp: TIMESTAMP_3,
264 | balance: U128(BALANCE_2),
265 | claimed_balance: U128(0)
266 | })
267 | );
268 | assert!(e.get_account(&e.users[2]).is_none());
269 | }
270 |
271 | #[test]
272 | fn test_claim() {
273 | let e = Env::init(3);
274 | e.root.borrow_runtime_mut().genesis.block_prod_time = 0;
275 | e.root.borrow_runtime_mut().cur_block.block_timestamp = to_nano(TIMESTAMP_1 - 100);
276 | assert_eq!(e.get_skyward_token_balance(&e.users[0]), 0);
277 | assert_eq!(
278 | e.get_stats(),
279 | Stats {
280 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
281 | skyward_account_id: SKYWARD_ID.to_string(),
282 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
283 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
284 | untouched_balance: U128(TOTAL_LOCKUP_BALANCE),
285 | total_claimed: U128(0)
286 | }
287 | );
288 | e.claim(&e.users[0]).assert_success();
289 | assert_eq!(e.get_skyward_token_balance(&e.users[0]), 0);
290 | assert_eq!(e.get_account(&e.users[0]).unwrap().claimed_balance.0, 0);
291 | assert_eq!(
292 | e.get_stats(),
293 | Stats {
294 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
295 | skyward_account_id: SKYWARD_ID.to_string(),
296 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
297 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
298 | untouched_balance: U128(TOTAL_LOCKUP_BALANCE - BALANCE_1),
299 | total_claimed: U128(0)
300 | }
301 | );
302 |
303 | assert_eq!(e.get_skyward_token_balance(&e.users[1]), 0);
304 | e.claim(&e.users[1]).assert_success();
305 | assert_eq!(e.get_account(&e.users[1]).unwrap().claimed_balance.0, 0);
306 | assert_eq!(
307 | e.get_stats(),
308 | Stats {
309 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
310 | skyward_account_id: SKYWARD_ID.to_string(),
311 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
312 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
313 | untouched_balance: U128(0),
314 | total_claimed: U128(0)
315 | }
316 | );
317 | assert_eq!(e.get_skyward_token_balance(&e.users[1]), 0);
318 |
319 | e.claim(&e.users[1]).assert_success();
320 | assert!(!e.claim(&e.users[2]).is_ok());
321 |
322 | e.root.borrow_runtime_mut().cur_block.block_timestamp =
323 | to_nano((TIMESTAMP_2 - TIMESTAMP_1) / 2 + TIMESTAMP_1);
324 | e.claim(&e.users[0]).assert_success();
325 | assert_eq!(
326 | e.get_account(&e.users[0]).unwrap().claimed_balance.0,
327 | BALANCE_1 / 2
328 | );
329 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1 / 2);
330 | assert_eq!(e.get_skyward_token_balance(&e.users[0]), BALANCE_1 / 2);
331 |
332 | e.claim(&e.users[1]).assert_success();
333 | assert_eq!(e.get_account(&e.users[1]).unwrap().claimed_balance.0, 0);
334 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1 / 2);
335 | assert_eq!(e.get_skyward_token_balance(&e.users[1]), 0);
336 |
337 | e.root.borrow_runtime_mut().cur_block.block_timestamp =
338 | to_nano((TIMESTAMP_3 - TIMESTAMP_1) / 2 + TIMESTAMP_1);
339 | e.claim(&e.users[0]).assert_success();
340 | assert_eq!(
341 | e.get_account(&e.users[0]).unwrap().claimed_balance.0,
342 | BALANCE_1
343 | );
344 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1);
345 | assert_eq!(e.get_skyward_token_balance(&e.users[0]), BALANCE_1);
346 |
347 | e.claim(&e.users[1]).assert_success();
348 | assert_eq!(
349 | e.get_account(&e.users[1]).unwrap().claimed_balance.0,
350 | BALANCE_2 / 2
351 | );
352 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1 + BALANCE_2 / 2);
353 | assert_eq!(e.get_skyward_token_balance(&e.users[1]), BALANCE_2 / 2);
354 |
355 | e.root.borrow_runtime_mut().cur_block.block_timestamp = to_nano(TIMESTAMP_3 + 100);
356 | e.claim(&e.users[0]).assert_success();
357 | assert_eq!(
358 | e.get_account(&e.users[0]).unwrap().claimed_balance.0,
359 | BALANCE_1
360 | );
361 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1 + BALANCE_2 / 2);
362 | assert_eq!(e.get_skyward_token_balance(&e.users[0]), BALANCE_1);
363 |
364 | e.claim(&e.users[1]).assert_success();
365 | assert_eq!(
366 | e.get_account(&e.users[1]).unwrap().claimed_balance.0,
367 | BALANCE_2
368 | );
369 | assert_eq!(e.get_stats().total_claimed.0, TOTAL_LOCKUP_BALANCE);
370 | assert_eq!(e.get_skyward_token_balance(&e.users[1]), BALANCE_2);
371 | }
372 |
373 | #[test]
374 | fn test_claim_unregistered() {
375 | let e = Env::init(0);
376 | e.root.borrow_runtime_mut().genesis.block_prod_time = 0;
377 | e.root.borrow_runtime_mut().cur_block.block_timestamp = to_nano(TIMESTAMP_1 - 100);
378 |
379 | let user = e.near.create_user(accounts(0).into(), to_yocto("100"));
380 |
381 | assert_eq!(e.get_skyward_token_balance(&user), 0);
382 | assert_eq!(
383 | e.get_stats(),
384 | Stats {
385 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
386 | skyward_account_id: SKYWARD_ID.to_string(),
387 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
388 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
389 | untouched_balance: U128(TOTAL_LOCKUP_BALANCE),
390 | total_claimed: U128(0)
391 | }
392 | );
393 | let res: bool = e.claim(&user).unwrap_json();
394 | assert!(res);
395 | assert_eq!(e.get_skyward_token_balance(&user), 0);
396 | assert_eq!(e.get_account(&user).unwrap().claimed_balance.0, 0);
397 | assert_eq!(
398 | e.get_stats(),
399 | Stats {
400 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
401 | skyward_account_id: SKYWARD_ID.to_string(),
402 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
403 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
404 | untouched_balance: U128(TOTAL_LOCKUP_BALANCE - BALANCE_1),
405 | total_claimed: U128(0)
406 | }
407 | );
408 |
409 | e.root.borrow_runtime_mut().cur_block.block_timestamp =
410 | to_nano((TIMESTAMP_2 - TIMESTAMP_1) / 2 + TIMESTAMP_1);
411 |
412 | // Not registered for storage on token
413 | let res: bool = e.claim(&user).unwrap_json();
414 | assert!(!res);
415 |
416 | assert_eq!(e.get_skyward_token_balance(&user), 0);
417 | assert_eq!(e.get_account(&user).unwrap().claimed_balance.0, 0);
418 |
419 | assert_eq!(
420 | e.get_stats(),
421 | Stats {
422 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
423 | skyward_account_id: SKYWARD_ID.to_string(),
424 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
425 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
426 | untouched_balance: U128(TOTAL_LOCKUP_BALANCE - BALANCE_1),
427 | total_claimed: U128(0)
428 | }
429 | );
430 |
431 | // Registering for storage
432 | storage_deposit(&user, SKYWARD_TOKEN_ID, &user.account_id);
433 |
434 | let res: bool = e.claim(&user).unwrap_json();
435 | assert!(res);
436 |
437 | assert_eq!(
438 | e.get_account(&user).unwrap().claimed_balance.0,
439 | BALANCE_1 / 2
440 | );
441 | assert_eq!(e.get_stats().total_claimed.0, BALANCE_1 / 2);
442 | assert_eq!(e.get_skyward_token_balance(&user), BALANCE_1 / 2);
443 | }
444 |
445 | #[test]
446 | fn test_miss_claim() {
447 | let e = Env::init(3);
448 | e.root.borrow_runtime_mut().genesis.block_prod_time = 0;
449 | e.root.borrow_runtime_mut().cur_block.block_timestamp = to_nano(TIMESTAMP_1 - 100);
450 | assert_eq!(e.get_treasury_circulating_supply(), SKYWARD_TOTAL_SUPPLY);
451 | e.claim(&e.users[1]).assert_success();
452 | assert_eq!(e.get_account(&e.users[1]).unwrap().claimed_balance.0, 0);
453 | assert_eq!(
454 | e.get_stats(),
455 | Stats {
456 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
457 | skyward_account_id: SKYWARD_ID.to_string(),
458 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
459 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
460 | untouched_balance: U128(BALANCE_1),
461 | total_claimed: U128(0)
462 | }
463 | );
464 | e.root.borrow_runtime_mut().cur_block.block_timestamp = to_nano(CLAIM_EXPIRATION_TIMESTAMP + 1);
465 |
466 | // Too late to claim
467 | assert!(!e.claim(&e.users[0]).is_ok());
468 |
469 | assert_eq!(
470 | e.get_stats(),
471 | Stats {
472 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
473 | skyward_account_id: SKYWARD_ID.to_string(),
474 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
475 | total_balance: U128(TOTAL_LOCKUP_BALANCE),
476 | untouched_balance: U128(BALANCE_1),
477 | total_claimed: U128(0)
478 | }
479 | );
480 |
481 | let skyward_initial_balance = e.skyward.account().unwrap().amount;
482 | let claim_initial_balance = e.skyward_claim.user_account.account().unwrap().amount;
483 | e.users[0]
484 | .function_call(e.skyward_claim.contract.donate_to_treasury(), DONATE_GAS, 0)
485 | .assert_success();
486 | let skyward_end_balance = e.skyward.account().unwrap().amount;
487 | let claim_end_balance = e.skyward_claim.user_account.account().unwrap().amount;
488 | assert!(claim_end_balance < claim_initial_balance);
489 | assert!(skyward_initial_balance < skyward_end_balance);
490 | let claim_balance_diff = claim_initial_balance - claim_end_balance;
491 | let skyward_balance_diff = skyward_end_balance - skyward_initial_balance;
492 | let gas_contract_reward_eps = to_yocto("0.001");
493 | assert!(claim_balance_diff > to_yocto("5"));
494 | assert!(claim_balance_diff + gas_contract_reward_eps > skyward_balance_diff);
495 | assert!(skyward_balance_diff + gas_contract_reward_eps > claim_balance_diff);
496 |
497 | assert_eq!(
498 | e.get_stats(),
499 | Stats {
500 | token_account_id: SKYWARD_TOKEN_ID.to_string(),
501 | skyward_account_id: SKYWARD_ID.to_string(),
502 | claim_expiration_timestamp: CLAIM_EXPIRATION_TIMESTAMP,
503 | total_balance: U128(BALANCE_2),
504 | untouched_balance: U128(0),
505 | total_claimed: U128(0)
506 | }
507 | );
508 | assert_eq!(
509 | e.get_treasury_circulating_supply(),
510 | SKYWARD_TOTAL_SUPPLY - BALANCE_1
511 | );
512 | }
513 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/.gitignore:
--------------------------------------------------------------------------------
1 | example_out\.borsh
2 | data/
3 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "Inflector"
5 | version = "0.11.4"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
8 |
9 | [[package]]
10 | name = "ahash"
11 | version = "0.4.7"
12 | source = "registry+https://github.com/rust-lang/crates.io-index"
13 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
14 |
15 | [[package]]
16 | name = "aho-corasick"
17 | version = "0.7.15"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
20 | dependencies = [
21 | "memchr",
22 | ]
23 |
24 | [[package]]
25 | name = "autocfg"
26 | version = "1.0.1"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
29 |
30 | [[package]]
31 | name = "base64"
32 | version = "0.13.0"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
35 |
36 | [[package]]
37 | name = "block-buffer"
38 | version = "0.9.0"
39 | source = "registry+https://github.com/rust-lang/crates.io-index"
40 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
41 | dependencies = [
42 | "block-padding",
43 | "generic-array",
44 | ]
45 |
46 | [[package]]
47 | name = "block-padding"
48 | version = "0.2.1"
49 | source = "registry+https://github.com/rust-lang/crates.io-index"
50 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
51 |
52 | [[package]]
53 | name = "borsh"
54 | version = "0.8.2"
55 | source = "registry+https://github.com/rust-lang/crates.io-index"
56 | checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
57 | dependencies = [
58 | "borsh-derive 0.8.2",
59 | "hashbrown",
60 | ]
61 |
62 | [[package]]
63 | name = "borsh"
64 | version = "0.9.0"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "4fcabb02816fdadf90866dc9a7824491ccb63d69f55375a266dc03509ac68d36"
67 | dependencies = [
68 | "borsh-derive 0.9.0",
69 | "hashbrown",
70 | ]
71 |
72 | [[package]]
73 | name = "borsh-derive"
74 | version = "0.8.2"
75 | source = "registry+https://github.com/rust-lang/crates.io-index"
76 | checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
77 | dependencies = [
78 | "borsh-derive-internal 0.8.2",
79 | "borsh-schema-derive-internal 0.8.2",
80 | "proc-macro-crate",
81 | "proc-macro2",
82 | "syn",
83 | ]
84 |
85 | [[package]]
86 | name = "borsh-derive"
87 | version = "0.9.0"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | checksum = "4bd16f0729b89f0a212b0e2e1d19cc6593df63f771161a11863967780e2d033d"
90 | dependencies = [
91 | "borsh-derive-internal 0.9.0",
92 | "borsh-schema-derive-internal 0.9.0",
93 | "proc-macro-crate",
94 | "proc-macro2",
95 | "syn",
96 | ]
97 |
98 | [[package]]
99 | name = "borsh-derive-internal"
100 | version = "0.8.2"
101 | source = "registry+https://github.com/rust-lang/crates.io-index"
102 | checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
103 | dependencies = [
104 | "proc-macro2",
105 | "quote",
106 | "syn",
107 | ]
108 |
109 | [[package]]
110 | name = "borsh-derive-internal"
111 | version = "0.9.0"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "1e321a130a3ac4b88eb59a6d670bde11eec9721a397b77e0f2079060e2a1b785"
114 | dependencies = [
115 | "proc-macro2",
116 | "quote",
117 | "syn",
118 | ]
119 |
120 | [[package]]
121 | name = "borsh-schema-derive-internal"
122 | version = "0.8.2"
123 | source = "registry+https://github.com/rust-lang/crates.io-index"
124 | checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
125 | dependencies = [
126 | "proc-macro2",
127 | "quote",
128 | "syn",
129 | ]
130 |
131 | [[package]]
132 | name = "borsh-schema-derive-internal"
133 | version = "0.9.0"
134 | source = "registry+https://github.com/rust-lang/crates.io-index"
135 | checksum = "15151a485164b319cc7a5160fe4316dc469a27993f71b73d7617dc9032ff0fd7"
136 | dependencies = [
137 | "proc-macro2",
138 | "quote",
139 | "syn",
140 | ]
141 |
142 | [[package]]
143 | name = "bs58"
144 | version = "0.4.0"
145 | source = "registry+https://github.com/rust-lang/crates.io-index"
146 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
147 |
148 | [[package]]
149 | name = "bstr"
150 | version = "0.2.15"
151 | source = "registry+https://github.com/rust-lang/crates.io-index"
152 | checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
153 | dependencies = [
154 | "lazy_static",
155 | "memchr",
156 | "regex-automata",
157 | "serde",
158 | ]
159 |
160 | [[package]]
161 | name = "byteorder"
162 | version = "1.4.3"
163 | source = "registry+https://github.com/rust-lang/crates.io-index"
164 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
165 |
166 | [[package]]
167 | name = "cfg-if"
168 | version = "0.1.10"
169 | source = "registry+https://github.com/rust-lang/crates.io-index"
170 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
171 |
172 | [[package]]
173 | name = "cfg-if"
174 | version = "1.0.0"
175 | source = "registry+https://github.com/rust-lang/crates.io-index"
176 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
177 |
178 | [[package]]
179 | name = "chrono"
180 | version = "0.4.19"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
183 | dependencies = [
184 | "libc",
185 | "num-integer",
186 | "num-traits",
187 | "time",
188 | "winapi",
189 | ]
190 |
191 | [[package]]
192 | name = "convert_case"
193 | version = "0.4.0"
194 | source = "registry+https://github.com/rust-lang/crates.io-index"
195 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
196 |
197 | [[package]]
198 | name = "cpufeatures"
199 | version = "0.1.1"
200 | source = "registry+https://github.com/rust-lang/crates.io-index"
201 | checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4"
202 | dependencies = [
203 | "libc",
204 | ]
205 |
206 | [[package]]
207 | name = "csv"
208 | version = "1.1.6"
209 | source = "registry+https://github.com/rust-lang/crates.io-index"
210 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
211 | dependencies = [
212 | "bstr",
213 | "csv-core",
214 | "itoa",
215 | "ryu",
216 | "serde",
217 | ]
218 |
219 | [[package]]
220 | name = "csv-core"
221 | version = "0.1.10"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
224 | dependencies = [
225 | "memchr",
226 | ]
227 |
228 | [[package]]
229 | name = "derive_more"
230 | version = "0.99.13"
231 | source = "registry+https://github.com/rust-lang/crates.io-index"
232 | checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6"
233 | dependencies = [
234 | "convert_case",
235 | "proc-macro2",
236 | "quote",
237 | "syn",
238 | ]
239 |
240 | [[package]]
241 | name = "digest"
242 | version = "0.9.0"
243 | source = "registry+https://github.com/rust-lang/crates.io-index"
244 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
245 | dependencies = [
246 | "generic-array",
247 | ]
248 |
249 | [[package]]
250 | name = "generic-array"
251 | version = "0.14.4"
252 | source = "registry+https://github.com/rust-lang/crates.io-index"
253 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
254 | dependencies = [
255 | "typenum",
256 | "version_check",
257 | ]
258 |
259 | [[package]]
260 | name = "hashbrown"
261 | version = "0.9.1"
262 | source = "registry+https://github.com/rust-lang/crates.io-index"
263 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
264 | dependencies = [
265 | "ahash",
266 | ]
267 |
268 | [[package]]
269 | name = "hex"
270 | version = "0.4.3"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
273 |
274 | [[package]]
275 | name = "indexmap"
276 | version = "1.6.2"
277 | source = "registry+https://github.com/rust-lang/crates.io-index"
278 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
279 | dependencies = [
280 | "autocfg",
281 | "hashbrown",
282 | ]
283 |
284 | [[package]]
285 | name = "itoa"
286 | version = "0.4.7"
287 | source = "registry+https://github.com/rust-lang/crates.io-index"
288 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
289 |
290 | [[package]]
291 | name = "keccak"
292 | version = "0.1.0"
293 | source = "registry+https://github.com/rust-lang/crates.io-index"
294 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
295 |
296 | [[package]]
297 | name = "lazy_static"
298 | version = "1.4.0"
299 | source = "registry+https://github.com/rust-lang/crates.io-index"
300 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
301 |
302 | [[package]]
303 | name = "libc"
304 | version = "0.2.94"
305 | source = "registry+https://github.com/rust-lang/crates.io-index"
306 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
307 |
308 | [[package]]
309 | name = "lockup_csv_to_borsh"
310 | version = "0.2.0"
311 | dependencies = [
312 | "borsh 0.9.0",
313 | "chrono",
314 | "csv",
315 | "near-sdk",
316 | "sha2",
317 | ]
318 |
319 | [[package]]
320 | name = "memchr"
321 | version = "2.3.4"
322 | source = "registry+https://github.com/rust-lang/crates.io-index"
323 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
324 |
325 | [[package]]
326 | name = "memory_units"
327 | version = "0.4.0"
328 | source = "registry+https://github.com/rust-lang/crates.io-index"
329 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
330 |
331 | [[package]]
332 | name = "near-primitives-core"
333 | version = "0.4.0"
334 | source = "registry+https://github.com/rust-lang/crates.io-index"
335 | checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52"
336 | dependencies = [
337 | "base64",
338 | "borsh 0.8.2",
339 | "bs58",
340 | "derive_more",
341 | "hex",
342 | "lazy_static",
343 | "num-rational",
344 | "serde",
345 | "serde_json",
346 | "sha2",
347 | ]
348 |
349 | [[package]]
350 | name = "near-rpc-error-core"
351 | version = "0.1.0"
352 | source = "registry+https://github.com/rust-lang/crates.io-index"
353 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9"
354 | dependencies = [
355 | "proc-macro2",
356 | "quote",
357 | "serde",
358 | "serde_json",
359 | "syn",
360 | ]
361 |
362 | [[package]]
363 | name = "near-rpc-error-macro"
364 | version = "0.1.0"
365 | source = "registry+https://github.com/rust-lang/crates.io-index"
366 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9"
367 | dependencies = [
368 | "near-rpc-error-core",
369 | "proc-macro2",
370 | "quote",
371 | "serde",
372 | "serde_json",
373 | "syn",
374 | ]
375 |
376 | [[package]]
377 | name = "near-runtime-utils"
378 | version = "4.0.0-pre.1"
379 | source = "registry+https://github.com/rust-lang/crates.io-index"
380 | checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07"
381 | dependencies = [
382 | "lazy_static",
383 | "regex",
384 | ]
385 |
386 | [[package]]
387 | name = "near-sdk"
388 | version = "3.1.0"
389 | source = "registry+https://github.com/rust-lang/crates.io-index"
390 | checksum = "c7383e242d3e07bf0951e8589d6eebd7f18bb1c1fc5fbec3fad796041a6aebd1"
391 | dependencies = [
392 | "base64",
393 | "borsh 0.8.2",
394 | "bs58",
395 | "near-primitives-core",
396 | "near-sdk-macros",
397 | "near-vm-logic",
398 | "serde",
399 | "serde_json",
400 | "wee_alloc",
401 | ]
402 |
403 | [[package]]
404 | name = "near-sdk-core"
405 | version = "3.1.0"
406 | source = "registry+https://github.com/rust-lang/crates.io-index"
407 | checksum = "284a78d9eb8eda58330462fa0023a6d7014c941df1f0387095e7dfd1dc0f2bce"
408 | dependencies = [
409 | "Inflector",
410 | "proc-macro2",
411 | "quote",
412 | "syn",
413 | ]
414 |
415 | [[package]]
416 | name = "near-sdk-macros"
417 | version = "3.1.0"
418 | source = "registry+https://github.com/rust-lang/crates.io-index"
419 | checksum = "2037337438f97d1ce5f7c896cf229dc56dacd5c01142d1ef95a7d778cde6ce7d"
420 | dependencies = [
421 | "near-sdk-core",
422 | "proc-macro2",
423 | "quote",
424 | "syn",
425 | ]
426 |
427 | [[package]]
428 | name = "near-vm-errors"
429 | version = "4.0.0-pre.1"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7"
432 | dependencies = [
433 | "borsh 0.8.2",
434 | "hex",
435 | "near-rpc-error-macro",
436 | "serde",
437 | ]
438 |
439 | [[package]]
440 | name = "near-vm-logic"
441 | version = "4.0.0-pre.1"
442 | source = "registry+https://github.com/rust-lang/crates.io-index"
443 | checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5"
444 | dependencies = [
445 | "base64",
446 | "borsh 0.8.2",
447 | "bs58",
448 | "byteorder",
449 | "near-primitives-core",
450 | "near-runtime-utils",
451 | "near-vm-errors",
452 | "serde",
453 | "sha2",
454 | "sha3",
455 | ]
456 |
457 | [[package]]
458 | name = "num-bigint"
459 | version = "0.3.2"
460 | source = "registry+https://github.com/rust-lang/crates.io-index"
461 | checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba"
462 | dependencies = [
463 | "autocfg",
464 | "num-integer",
465 | "num-traits",
466 | ]
467 |
468 | [[package]]
469 | name = "num-integer"
470 | version = "0.1.44"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
473 | dependencies = [
474 | "autocfg",
475 | "num-traits",
476 | ]
477 |
478 | [[package]]
479 | name = "num-rational"
480 | version = "0.3.2"
481 | source = "registry+https://github.com/rust-lang/crates.io-index"
482 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
483 | dependencies = [
484 | "autocfg",
485 | "num-bigint",
486 | "num-integer",
487 | "num-traits",
488 | "serde",
489 | ]
490 |
491 | [[package]]
492 | name = "num-traits"
493 | version = "0.2.14"
494 | source = "registry+https://github.com/rust-lang/crates.io-index"
495 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
496 | dependencies = [
497 | "autocfg",
498 | ]
499 |
500 | [[package]]
501 | name = "opaque-debug"
502 | version = "0.3.0"
503 | source = "registry+https://github.com/rust-lang/crates.io-index"
504 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
505 |
506 | [[package]]
507 | name = "proc-macro-crate"
508 | version = "0.1.5"
509 | source = "registry+https://github.com/rust-lang/crates.io-index"
510 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
511 | dependencies = [
512 | "toml",
513 | ]
514 |
515 | [[package]]
516 | name = "proc-macro2"
517 | version = "1.0.26"
518 | source = "registry+https://github.com/rust-lang/crates.io-index"
519 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
520 | dependencies = [
521 | "unicode-xid",
522 | ]
523 |
524 | [[package]]
525 | name = "quote"
526 | version = "1.0.9"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
529 | dependencies = [
530 | "proc-macro2",
531 | ]
532 |
533 | [[package]]
534 | name = "regex"
535 | version = "1.4.6"
536 | source = "registry+https://github.com/rust-lang/crates.io-index"
537 | checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
538 | dependencies = [
539 | "aho-corasick",
540 | "memchr",
541 | "regex-syntax",
542 | ]
543 |
544 | [[package]]
545 | name = "regex-automata"
546 | version = "0.1.9"
547 | source = "registry+https://github.com/rust-lang/crates.io-index"
548 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
549 | dependencies = [
550 | "byteorder",
551 | ]
552 |
553 | [[package]]
554 | name = "regex-syntax"
555 | version = "0.6.25"
556 | source = "registry+https://github.com/rust-lang/crates.io-index"
557 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
558 |
559 | [[package]]
560 | name = "ryu"
561 | version = "1.0.5"
562 | source = "registry+https://github.com/rust-lang/crates.io-index"
563 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
564 |
565 | [[package]]
566 | name = "serde"
567 | version = "1.0.118"
568 | source = "registry+https://github.com/rust-lang/crates.io-index"
569 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
570 | dependencies = [
571 | "serde_derive",
572 | ]
573 |
574 | [[package]]
575 | name = "serde_derive"
576 | version = "1.0.118"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
579 | dependencies = [
580 | "proc-macro2",
581 | "quote",
582 | "syn",
583 | ]
584 |
585 | [[package]]
586 | name = "serde_json"
587 | version = "1.0.64"
588 | source = "registry+https://github.com/rust-lang/crates.io-index"
589 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
590 | dependencies = [
591 | "indexmap",
592 | "itoa",
593 | "ryu",
594 | "serde",
595 | ]
596 |
597 | [[package]]
598 | name = "sha2"
599 | version = "0.9.5"
600 | source = "registry+https://github.com/rust-lang/crates.io-index"
601 | checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
602 | dependencies = [
603 | "block-buffer",
604 | "cfg-if 1.0.0",
605 | "cpufeatures",
606 | "digest",
607 | "opaque-debug",
608 | ]
609 |
610 | [[package]]
611 | name = "sha3"
612 | version = "0.9.1"
613 | source = "registry+https://github.com/rust-lang/crates.io-index"
614 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
615 | dependencies = [
616 | "block-buffer",
617 | "digest",
618 | "keccak",
619 | "opaque-debug",
620 | ]
621 |
622 | [[package]]
623 | name = "syn"
624 | version = "1.0.57"
625 | source = "registry+https://github.com/rust-lang/crates.io-index"
626 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
627 | dependencies = [
628 | "proc-macro2",
629 | "quote",
630 | "unicode-xid",
631 | ]
632 |
633 | [[package]]
634 | name = "time"
635 | version = "0.1.44"
636 | source = "registry+https://github.com/rust-lang/crates.io-index"
637 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
638 | dependencies = [
639 | "libc",
640 | "wasi",
641 | "winapi",
642 | ]
643 |
644 | [[package]]
645 | name = "toml"
646 | version = "0.5.8"
647 | source = "registry+https://github.com/rust-lang/crates.io-index"
648 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
649 | dependencies = [
650 | "serde",
651 | ]
652 |
653 | [[package]]
654 | name = "typenum"
655 | version = "1.13.0"
656 | source = "registry+https://github.com/rust-lang/crates.io-index"
657 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
658 |
659 | [[package]]
660 | name = "unicode-xid"
661 | version = "0.2.1"
662 | source = "registry+https://github.com/rust-lang/crates.io-index"
663 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
664 |
665 | [[package]]
666 | name = "version_check"
667 | version = "0.9.3"
668 | source = "registry+https://github.com/rust-lang/crates.io-index"
669 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
670 |
671 | [[package]]
672 | name = "wasi"
673 | version = "0.10.0+wasi-snapshot-preview1"
674 | source = "registry+https://github.com/rust-lang/crates.io-index"
675 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
676 |
677 | [[package]]
678 | name = "wee_alloc"
679 | version = "0.4.5"
680 | source = "registry+https://github.com/rust-lang/crates.io-index"
681 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
682 | dependencies = [
683 | "cfg-if 0.1.10",
684 | "libc",
685 | "memory_units",
686 | "winapi",
687 | ]
688 |
689 | [[package]]
690 | name = "winapi"
691 | version = "0.3.9"
692 | source = "registry+https://github.com/rust-lang/crates.io-index"
693 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
694 | dependencies = [
695 | "winapi-i686-pc-windows-gnu",
696 | "winapi-x86_64-pc-windows-gnu",
697 | ]
698 |
699 | [[package]]
700 | name = "winapi-i686-pc-windows-gnu"
701 | version = "0.4.0"
702 | source = "registry+https://github.com/rust-lang/crates.io-index"
703 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
704 |
705 | [[package]]
706 | name = "winapi-x86_64-pc-windows-gnu"
707 | version = "0.4.0"
708 | source = "registry+https://github.com/rust-lang/crates.io-index"
709 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
710 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lockup_csv_to_borsh"
3 | version = "0.2.0"
4 | authors = ["Spensa Nightshade "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | csv = "1.1.6"
9 | borsh = "0.9.0"
10 | near-sdk = "3.1.0"
11 | chrono = "0.4"
12 | sha2 = "0.9.0"
13 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/README.md:
--------------------------------------------------------------------------------
1 | # Tool for lockups
2 |
3 | Converts a given csv file into a lockup binary input for efficient lockup deployments.
4 |
5 | ## Run
6 |
7 | The tool takes up to 3 arguments:
8 | - Input csv filename
9 | - Output filename
10 | - Optional balance multiplier. It's convenient to put balances in the CSV file to something that is human-readable.By default `1`.
11 |
12 | ```bash
13 | cargo run -- example.csv example_out.borsh 1000000000000000
14 | ```
15 |
16 | ## CSV format
17 |
18 | The CSV contains 5 fields:
19 | - Account ID for the lockup. Has to be a valid account ID.
20 | - The start date in the following format `YYYY-MM-DD`.
21 | - Optional cliff date in the following format `YYYY-MM-DD`.
22 | - The end date in the following format `YYYY-MM-DD`.
23 | - The integer balance. It'll be multiplied by the balance multiplier argument.
24 |
25 | E.g. `example.csv`
26 | ```csv
27 | account_id,start_date,cliff_date,end_date,balance
28 | alice.near,2021-06-01,,2021-07-01,10
29 | bob.near,2021-06-01,2021-07-01,2021-10-01,10000
30 | ```
31 |
32 | Assuming:
33 | - The given balance multiplier is `15` decimals (`1000000000000000`).
34 | - And the FT has `18` decimals or (`1000000000000000000`).
35 |
36 | The lockup setup has 2 accounts:
37 | - `alice.near` has linear vesting of `0.01` tokens for 1 month (starting from `June 1, 2021` to `July 1, 2021` in UTC time).
38 | - `bob.near` has linear vesting of `10` tokens for 4 months and 1 month cliff (starting from `June 1, 2021`, with a cliff at `July 1, 2021` and ends in `October 1, 2021` in UTC time).
39 |
40 | The output info:
41 | ```console
42 | Total number of accounts 2
43 | Total balance: 10010
44 | Total multiplied balance: 10010000000000000000
45 | Minimum start timestamp: 1622505600
46 | Maximum end timestamp: 1633046400
47 | ```
48 |
49 | And produces the binary `example_out.borsh`
50 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/example.csv:
--------------------------------------------------------------------------------
1 | account_id,start_date,cliff_date,end_date,balance
2 | alice.near,2021-06-01,,2021-07-01,10000000
3 | bob.near,2021-06-01,2021-07-01,2021-10-01,10000000000
4 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | cargo run -- data/lockup.csv data/lockup 1000000000
6 | mkdir -p "../lockup/data"
7 | cp data/lockup*.borsh ../lockup/data/
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/run_example.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | mkdir -p ./data
6 | cp ./example.csv ./data/lockup.csv
7 | ./run.sh
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/lockup_csv_to_borsh/src/main.rs:
--------------------------------------------------------------------------------
1 | use chrono::NaiveDate;
2 | use near_sdk::borsh::{self, BorshSerialize};
3 | use near_sdk::json_types::ValidAccountId;
4 | use near_sdk::serde::Deserialize;
5 | use near_sdk::{AccountId, CryptoHash};
6 | use std::collections::BTreeMap;
7 | use std::env;
8 | use std::fs::File;
9 | use std::io::Write;
10 |
11 | #[derive(Debug, Deserialize)]
12 | #[serde(crate = "near_sdk::serde")]
13 | struct Record {
14 | pub account_id: ValidAccountId,
15 | pub start_date: String,
16 | pub cliff_date: String,
17 | pub end_date: String,
18 | pub balance: u128,
19 | }
20 |
21 | #[derive(BorshSerialize)]
22 | pub struct FixedSizeAccount {
23 | pub account_hash: CryptoHash,
24 | pub start_timestamp: u32,
25 | pub cliff_timestamp: u32,
26 | pub end_timestamp: u32,
27 | pub balance: u128,
28 | }
29 |
30 | fn parse_date(s: &str) -> Option {
31 | let dt = NaiveDate::parse_from_str(s, "%Y-%m-%d").ok()?;
32 | Some(dt.and_hms(0, 0, 0).timestamp() as u32)
33 | }
34 |
35 | fn hash_account(account_id: &AccountId) -> CryptoHash {
36 | use sha2::Digest;
37 |
38 | let value_hash = sha2::Sha256::digest(account_id.as_bytes());
39 | let mut res = CryptoHash::default();
40 | res.copy_from_slice(&value_hash);
41 |
42 | res
43 | }
44 |
45 | const MAX_PER_FILE: usize = 10000;
46 |
47 | pub fn main() {
48 | let file_path = env::args_os()
49 | .nth(1)
50 | .expect("Missing input csv file name argument");
51 | let output_file_path = env::args_os()
52 | .nth(2)
53 | .expect("Missing output borsh file name argument")
54 | .into_string()
55 | .unwrap();
56 | let balance_multiplier: u128 = env::args_os()
57 | .nth(3)
58 | .map(|s| {
59 | s.into_string()
60 | .expect("Failed to parse balance multiplier")
61 | .parse()
62 | .unwrap()
63 | })
64 | .unwrap_or(1);
65 | assert!(
66 | balance_multiplier > 0,
67 | "Balance multiplier should be positive"
68 | );
69 | let file = File::open(file_path).unwrap();
70 | let mut rdr = csv::Reader::from_reader(file);
71 | let mut total_accounts: usize = 0;
72 | let mut total_balance: u128 = 0;
73 | let mut min_start_timestamp = u32::MAX;
74 | let mut max_end_timestamp = 0;
75 | let mut accounts = BTreeMap::new();
76 | for result in rdr.deserialize() {
77 | let Record {
78 | account_id,
79 | start_date,
80 | cliff_date,
81 | end_date,
82 | balance,
83 | } = result.unwrap();
84 | let account_id_str: AccountId = account_id.into();
85 | let start_timestamp = parse_date(&start_date).unwrap();
86 | let cliff_timestamp = parse_date(&cliff_date).unwrap_or(start_timestamp);
87 | let end_timestamp = parse_date(&end_date).unwrap();
88 | assert!(start_timestamp <= cliff_timestamp);
89 | assert!(cliff_timestamp <= end_timestamp);
90 | assert!(start_timestamp < end_timestamp);
91 | assert!(balance > 0);
92 | min_start_timestamp = std::cmp::min(min_start_timestamp, start_timestamp);
93 | max_end_timestamp = std::cmp::max(max_end_timestamp, end_timestamp);
94 | let account_hash = hash_account(&account_id_str);
95 | let balance = balance
96 | .checked_mul(balance_multiplier)
97 | .expect("Balance multiplication overflow");
98 | total_accounts += 1;
99 | total_balance = total_balance
100 | .checked_add(balance)
101 | .expect("Total balance overflow");
102 | println!(
103 | "{:30} -> {} {} {} -> {}",
104 | balance, start_timestamp, cliff_timestamp, end_timestamp, account_id_str
105 | );
106 | let account = FixedSizeAccount {
107 | account_hash,
108 | start_timestamp,
109 | cliff_timestamp,
110 | end_timestamp,
111 | balance,
112 | };
113 | assert!(accounts.insert(account_hash, account).is_none());
114 | }
115 | println!("Total number of accounts {}\nTotal balance: {}\nTotal multiplied balance: {}\nMinimum start timestamp: {}\nMaximum end timestamp: {}",
116 | total_accounts,
117 | total_balance / balance_multiplier,
118 | total_balance,
119 | min_start_timestamp,
120 | max_end_timestamp,
121 | );
122 |
123 | let mut index = 0;
124 | let values: Vec<_> = accounts.values().collect();
125 | for chunk in values.chunks(MAX_PER_FILE) {
126 | let output_file = format!("{}{}.borsh", output_file_path, index);
127 | let mut total_balance = 0;
128 | let mut data = vec![];
129 | for account in chunk {
130 | total_balance += account.balance;
131 | data.extend(account.try_to_vec().unwrap());
132 | }
133 | println!("File {}: balance {}", output_file, total_balance);
134 | let mut file = File::create(output_file).expect("Failed to create the output file");
135 | file.write_all(&data).expect("Failed to write data");
136 | index += 1;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/permissions/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "Inflector"
5 | version = "0.11.4"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
8 |
9 | [[package]]
10 | name = "ahash"
11 | version = "0.4.7"
12 | source = "registry+https://github.com/rust-lang/crates.io-index"
13 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
14 |
15 | [[package]]
16 | name = "aho-corasick"
17 | version = "0.7.18"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
20 | dependencies = [
21 | "memchr",
22 | ]
23 |
24 | [[package]]
25 | name = "autocfg"
26 | version = "1.0.1"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
29 |
30 | [[package]]
31 | name = "base64"
32 | version = "0.13.0"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
35 |
36 | [[package]]
37 | name = "block-buffer"
38 | version = "0.9.0"
39 | source = "registry+https://github.com/rust-lang/crates.io-index"
40 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
41 | dependencies = [
42 | "block-padding",
43 | "generic-array",
44 | ]
45 |
46 | [[package]]
47 | name = "block-padding"
48 | version = "0.2.1"
49 | source = "registry+https://github.com/rust-lang/crates.io-index"
50 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
51 |
52 | [[package]]
53 | name = "borsh"
54 | version = "0.8.2"
55 | source = "registry+https://github.com/rust-lang/crates.io-index"
56 | checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
57 | dependencies = [
58 | "borsh-derive",
59 | "hashbrown",
60 | ]
61 |
62 | [[package]]
63 | name = "borsh-derive"
64 | version = "0.8.2"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
67 | dependencies = [
68 | "borsh-derive-internal",
69 | "borsh-schema-derive-internal",
70 | "proc-macro-crate",
71 | "proc-macro2",
72 | "syn",
73 | ]
74 |
75 | [[package]]
76 | name = "borsh-derive-internal"
77 | version = "0.8.2"
78 | source = "registry+https://github.com/rust-lang/crates.io-index"
79 | checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
80 | dependencies = [
81 | "proc-macro2",
82 | "quote",
83 | "syn",
84 | ]
85 |
86 | [[package]]
87 | name = "borsh-schema-derive-internal"
88 | version = "0.8.2"
89 | source = "registry+https://github.com/rust-lang/crates.io-index"
90 | checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
91 | dependencies = [
92 | "proc-macro2",
93 | "quote",
94 | "syn",
95 | ]
96 |
97 | [[package]]
98 | name = "bs58"
99 | version = "0.4.0"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
102 |
103 | [[package]]
104 | name = "byteorder"
105 | version = "1.4.3"
106 | source = "registry+https://github.com/rust-lang/crates.io-index"
107 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
108 |
109 | [[package]]
110 | name = "cfg-if"
111 | version = "0.1.10"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
114 |
115 | [[package]]
116 | name = "cfg-if"
117 | version = "1.0.0"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
120 |
121 | [[package]]
122 | name = "convert_case"
123 | version = "0.4.0"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
126 |
127 | [[package]]
128 | name = "cpufeatures"
129 | version = "0.1.4"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
132 | dependencies = [
133 | "libc",
134 | ]
135 |
136 | [[package]]
137 | name = "derive_more"
138 | version = "0.99.14"
139 | source = "registry+https://github.com/rust-lang/crates.io-index"
140 | checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
141 | dependencies = [
142 | "convert_case",
143 | "proc-macro2",
144 | "quote",
145 | "syn",
146 | ]
147 |
148 | [[package]]
149 | name = "digest"
150 | version = "0.9.0"
151 | source = "registry+https://github.com/rust-lang/crates.io-index"
152 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
153 | dependencies = [
154 | "generic-array",
155 | ]
156 |
157 | [[package]]
158 | name = "generic-array"
159 | version = "0.14.4"
160 | source = "registry+https://github.com/rust-lang/crates.io-index"
161 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
162 | dependencies = [
163 | "typenum",
164 | "version_check",
165 | ]
166 |
167 | [[package]]
168 | name = "hashbrown"
169 | version = "0.9.1"
170 | source = "registry+https://github.com/rust-lang/crates.io-index"
171 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
172 | dependencies = [
173 | "ahash",
174 | ]
175 |
176 | [[package]]
177 | name = "hex"
178 | version = "0.4.3"
179 | source = "registry+https://github.com/rust-lang/crates.io-index"
180 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
181 |
182 | [[package]]
183 | name = "indexmap"
184 | version = "1.6.2"
185 | source = "registry+https://github.com/rust-lang/crates.io-index"
186 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
187 | dependencies = [
188 | "autocfg",
189 | "hashbrown",
190 | ]
191 |
192 | [[package]]
193 | name = "itoa"
194 | version = "0.4.7"
195 | source = "registry+https://github.com/rust-lang/crates.io-index"
196 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
197 |
198 | [[package]]
199 | name = "keccak"
200 | version = "0.1.0"
201 | source = "registry+https://github.com/rust-lang/crates.io-index"
202 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
203 |
204 | [[package]]
205 | name = "lazy_static"
206 | version = "1.4.0"
207 | source = "registry+https://github.com/rust-lang/crates.io-index"
208 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
209 |
210 | [[package]]
211 | name = "libc"
212 | version = "0.2.96"
213 | source = "registry+https://github.com/rust-lang/crates.io-index"
214 | checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc"
215 |
216 | [[package]]
217 | name = "memchr"
218 | version = "2.4.0"
219 | source = "registry+https://github.com/rust-lang/crates.io-index"
220 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
221 |
222 | [[package]]
223 | name = "memory_units"
224 | version = "0.4.0"
225 | source = "registry+https://github.com/rust-lang/crates.io-index"
226 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
227 |
228 | [[package]]
229 | name = "near-primitives-core"
230 | version = "0.4.0"
231 | source = "registry+https://github.com/rust-lang/crates.io-index"
232 | checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52"
233 | dependencies = [
234 | "base64",
235 | "borsh",
236 | "bs58",
237 | "derive_more",
238 | "hex",
239 | "lazy_static",
240 | "num-rational",
241 | "serde",
242 | "serde_json",
243 | "sha2",
244 | ]
245 |
246 | [[package]]
247 | name = "near-rpc-error-core"
248 | version = "0.1.0"
249 | source = "registry+https://github.com/rust-lang/crates.io-index"
250 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9"
251 | dependencies = [
252 | "proc-macro2",
253 | "quote",
254 | "serde",
255 | "serde_json",
256 | "syn",
257 | ]
258 |
259 | [[package]]
260 | name = "near-rpc-error-macro"
261 | version = "0.1.0"
262 | source = "registry+https://github.com/rust-lang/crates.io-index"
263 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9"
264 | dependencies = [
265 | "near-rpc-error-core",
266 | "proc-macro2",
267 | "quote",
268 | "serde",
269 | "serde_json",
270 | "syn",
271 | ]
272 |
273 | [[package]]
274 | name = "near-runtime-utils"
275 | version = "4.0.0-pre.1"
276 | source = "registry+https://github.com/rust-lang/crates.io-index"
277 | checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07"
278 | dependencies = [
279 | "lazy_static",
280 | "regex",
281 | ]
282 |
283 | [[package]]
284 | name = "near-sdk"
285 | version = "3.1.0"
286 | source = "registry+https://github.com/rust-lang/crates.io-index"
287 | checksum = "c7383e242d3e07bf0951e8589d6eebd7f18bb1c1fc5fbec3fad796041a6aebd1"
288 | dependencies = [
289 | "base64",
290 | "borsh",
291 | "bs58",
292 | "near-primitives-core",
293 | "near-sdk-macros",
294 | "near-vm-logic",
295 | "serde",
296 | "serde_json",
297 | "wee_alloc",
298 | ]
299 |
300 | [[package]]
301 | name = "near-sdk-core"
302 | version = "3.1.0"
303 | source = "registry+https://github.com/rust-lang/crates.io-index"
304 | checksum = "284a78d9eb8eda58330462fa0023a6d7014c941df1f0387095e7dfd1dc0f2bce"
305 | dependencies = [
306 | "Inflector",
307 | "proc-macro2",
308 | "quote",
309 | "syn",
310 | ]
311 |
312 | [[package]]
313 | name = "near-sdk-macros"
314 | version = "3.1.0"
315 | source = "registry+https://github.com/rust-lang/crates.io-index"
316 | checksum = "2037337438f97d1ce5f7c896cf229dc56dacd5c01142d1ef95a7d778cde6ce7d"
317 | dependencies = [
318 | "near-sdk-core",
319 | "proc-macro2",
320 | "quote",
321 | "syn",
322 | ]
323 |
324 | [[package]]
325 | name = "near-vm-errors"
326 | version = "4.0.0-pre.1"
327 | source = "registry+https://github.com/rust-lang/crates.io-index"
328 | checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7"
329 | dependencies = [
330 | "borsh",
331 | "hex",
332 | "near-rpc-error-macro",
333 | "serde",
334 | ]
335 |
336 | [[package]]
337 | name = "near-vm-logic"
338 | version = "4.0.0-pre.1"
339 | source = "registry+https://github.com/rust-lang/crates.io-index"
340 | checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5"
341 | dependencies = [
342 | "base64",
343 | "borsh",
344 | "bs58",
345 | "byteorder",
346 | "near-primitives-core",
347 | "near-runtime-utils",
348 | "near-vm-errors",
349 | "serde",
350 | "sha2",
351 | "sha3",
352 | ]
353 |
354 | [[package]]
355 | name = "num-bigint"
356 | version = "0.3.2"
357 | source = "registry+https://github.com/rust-lang/crates.io-index"
358 | checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba"
359 | dependencies = [
360 | "autocfg",
361 | "num-integer",
362 | "num-traits",
363 | ]
364 |
365 | [[package]]
366 | name = "num-integer"
367 | version = "0.1.44"
368 | source = "registry+https://github.com/rust-lang/crates.io-index"
369 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
370 | dependencies = [
371 | "autocfg",
372 | "num-traits",
373 | ]
374 |
375 | [[package]]
376 | name = "num-rational"
377 | version = "0.3.2"
378 | source = "registry+https://github.com/rust-lang/crates.io-index"
379 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
380 | dependencies = [
381 | "autocfg",
382 | "num-bigint",
383 | "num-integer",
384 | "num-traits",
385 | "serde",
386 | ]
387 |
388 | [[package]]
389 | name = "num-traits"
390 | version = "0.2.14"
391 | source = "registry+https://github.com/rust-lang/crates.io-index"
392 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
393 | dependencies = [
394 | "autocfg",
395 | ]
396 |
397 | [[package]]
398 | name = "opaque-debug"
399 | version = "0.3.0"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
402 |
403 | [[package]]
404 | name = "permissions"
405 | version = "0.1.0"
406 | dependencies = [
407 | "near-sdk",
408 | ]
409 |
410 | [[package]]
411 | name = "proc-macro-crate"
412 | version = "0.1.5"
413 | source = "registry+https://github.com/rust-lang/crates.io-index"
414 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
415 | dependencies = [
416 | "toml",
417 | ]
418 |
419 | [[package]]
420 | name = "proc-macro2"
421 | version = "1.0.27"
422 | source = "registry+https://github.com/rust-lang/crates.io-index"
423 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
424 | dependencies = [
425 | "unicode-xid",
426 | ]
427 |
428 | [[package]]
429 | name = "quote"
430 | version = "1.0.9"
431 | source = "registry+https://github.com/rust-lang/crates.io-index"
432 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
433 | dependencies = [
434 | "proc-macro2",
435 | ]
436 |
437 | [[package]]
438 | name = "regex"
439 | version = "1.5.4"
440 | source = "registry+https://github.com/rust-lang/crates.io-index"
441 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
442 | dependencies = [
443 | "aho-corasick",
444 | "memchr",
445 | "regex-syntax",
446 | ]
447 |
448 | [[package]]
449 | name = "regex-syntax"
450 | version = "0.6.25"
451 | source = "registry+https://github.com/rust-lang/crates.io-index"
452 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
453 |
454 | [[package]]
455 | name = "ryu"
456 | version = "1.0.5"
457 | source = "registry+https://github.com/rust-lang/crates.io-index"
458 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
459 |
460 | [[package]]
461 | name = "serde"
462 | version = "1.0.118"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
465 | dependencies = [
466 | "serde_derive",
467 | ]
468 |
469 | [[package]]
470 | name = "serde_derive"
471 | version = "1.0.118"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
474 | dependencies = [
475 | "proc-macro2",
476 | "quote",
477 | "syn",
478 | ]
479 |
480 | [[package]]
481 | name = "serde_json"
482 | version = "1.0.64"
483 | source = "registry+https://github.com/rust-lang/crates.io-index"
484 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
485 | dependencies = [
486 | "indexmap",
487 | "itoa",
488 | "ryu",
489 | "serde",
490 | ]
491 |
492 | [[package]]
493 | name = "sha2"
494 | version = "0.9.5"
495 | source = "registry+https://github.com/rust-lang/crates.io-index"
496 | checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
497 | dependencies = [
498 | "block-buffer",
499 | "cfg-if 1.0.0",
500 | "cpufeatures",
501 | "digest",
502 | "opaque-debug",
503 | ]
504 |
505 | [[package]]
506 | name = "sha3"
507 | version = "0.9.1"
508 | source = "registry+https://github.com/rust-lang/crates.io-index"
509 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
510 | dependencies = [
511 | "block-buffer",
512 | "digest",
513 | "keccak",
514 | "opaque-debug",
515 | ]
516 |
517 | [[package]]
518 | name = "syn"
519 | version = "1.0.57"
520 | source = "registry+https://github.com/rust-lang/crates.io-index"
521 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
522 | dependencies = [
523 | "proc-macro2",
524 | "quote",
525 | "unicode-xid",
526 | ]
527 |
528 | [[package]]
529 | name = "toml"
530 | version = "0.5.8"
531 | source = "registry+https://github.com/rust-lang/crates.io-index"
532 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
533 | dependencies = [
534 | "serde",
535 | ]
536 |
537 | [[package]]
538 | name = "typenum"
539 | version = "1.13.0"
540 | source = "registry+https://github.com/rust-lang/crates.io-index"
541 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
542 |
543 | [[package]]
544 | name = "unicode-xid"
545 | version = "0.2.2"
546 | source = "registry+https://github.com/rust-lang/crates.io-index"
547 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
548 |
549 | [[package]]
550 | name = "version_check"
551 | version = "0.9.3"
552 | source = "registry+https://github.com/rust-lang/crates.io-index"
553 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
554 |
555 | [[package]]
556 | name = "wee_alloc"
557 | version = "0.4.5"
558 | source = "registry+https://github.com/rust-lang/crates.io-index"
559 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
560 | dependencies = [
561 | "cfg-if 0.1.10",
562 | "libc",
563 | "memory_units",
564 | "winapi",
565 | ]
566 |
567 | [[package]]
568 | name = "winapi"
569 | version = "0.3.9"
570 | source = "registry+https://github.com/rust-lang/crates.io-index"
571 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
572 | dependencies = [
573 | "winapi-i686-pc-windows-gnu",
574 | "winapi-x86_64-pc-windows-gnu",
575 | ]
576 |
577 | [[package]]
578 | name = "winapi-i686-pc-windows-gnu"
579 | version = "0.4.0"
580 | source = "registry+https://github.com/rust-lang/crates.io-index"
581 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
582 |
583 | [[package]]
584 | name = "winapi-x86_64-pc-windows-gnu"
585 | version = "0.4.0"
586 | source = "registry+https://github.com/rust-lang/crates.io-index"
587 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
588 |
--------------------------------------------------------------------------------
/permissions/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "permissions"
3 | version = "0.1.0"
4 | authors = ["Spensa Nightshade "]
5 | edition = "2018"
6 |
7 | [lib]
8 | crate-type = ["cdylib", "rlib"]
9 |
10 | [dependencies]
11 | near-sdk = "3.1.0"
12 |
13 | [profile.release]
14 | codegen-units=1
15 | opt-level = "z"
16 | lto = true
17 | debug = false
18 | panic = "abort"
19 | overflow-checks = true
20 |
--------------------------------------------------------------------------------
/permissions/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
6 | mkdir -p ./res
7 | cp target/wasm32-unknown-unknown/release/permissions.wasm ./res/
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/permissions/src/lib.rs:
--------------------------------------------------------------------------------
1 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
2 | use near_sdk::collections::LookupSet;
3 | use near_sdk::json_types::ValidAccountId;
4 | use near_sdk::{env, near_bindgen, AccountId, BorshStorageKey, PanicOnDefault};
5 |
6 | near_sdk::setup_alloc!();
7 |
8 | #[derive(BorshStorageKey, BorshSerialize)]
9 | pub(crate) enum StorageKey {
10 | Accounts,
11 | }
12 |
13 | #[near_bindgen]
14 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
15 | pub struct Contract {
16 | pub approved_accounts: LookupSet,
17 |
18 | pub owner_id: AccountId,
19 | }
20 |
21 | #[near_bindgen]
22 | impl Contract {
23 | #[init]
24 | pub fn new(owner_id: ValidAccountId) -> Self {
25 | Self {
26 | approved_accounts: LookupSet::new(StorageKey::Accounts),
27 | owner_id: owner_id.into(),
28 | }
29 | }
30 |
31 | pub fn is_permissions_contract(&self) -> bool {
32 | true
33 | }
34 |
35 | #[allow(unused_variables)]
36 | pub fn is_approved(&self, account_id: ValidAccountId, sale_id: u64) -> bool {
37 | self.approved_accounts.contains(account_id.as_ref())
38 | }
39 |
40 | pub fn approve(&mut self, account_id: ValidAccountId) {
41 | self.assert_called_by_owner();
42 | self.approved_accounts.insert(account_id.as_ref());
43 | }
44 |
45 | pub fn reject(&mut self, account_id: ValidAccountId) {
46 | self.assert_called_by_owner();
47 | self.approved_accounts.remove(account_id.as_ref());
48 | }
49 | }
50 |
51 | impl Contract {
52 | fn assert_called_by_owner(&self) {
53 | assert_eq!(&self.owner_id, &env::predecessor_account_id());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/release/lockup0.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/release/lockup0.wasm
--------------------------------------------------------------------------------
/release/lockup1.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/release/lockup1.wasm
--------------------------------------------------------------------------------
/release/lockup2.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/release/lockup2.wasm
--------------------------------------------------------------------------------
/release/lockup3.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/release/lockup3.wasm
--------------------------------------------------------------------------------
/release/skyward.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skyward-finance/contracts/142e76303ac9d15f4efb7affde4e60896ccddbfb/release/skyward.wasm
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | cd "$(dirname $0)"
4 |
5 | [ "$#" -eq 1 ] || die "One Account ID argument required, $# provided"
6 |
7 | export ACCOUNT_ID=$1
8 | ONE_YOCTO=0.000000000000000000000001
9 |
10 | export SKYWARD_TOKEN_ID=token.$ACCOUNT_ID
11 | export CONTRACT_ID=$ACCOUNT_ID
12 |
13 | near create-account $SKYWARD_TOKEN_ID --masterAccount=$ACCOUNT_ID --initialBalance=3
14 | near deploy $SKYWARD_TOKEN_ID common/fungible_token.wasm new '{"owner_id": "'$ACCOUNT_ID'", "total_supply": "1000000000000000000000000", "metadata": {"spec": "ft-1.0.0", "name": "Skyward Finance Token", "symbol": "SKYWARD", "icon": "data:image/png;base64,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", "decimals": 18}}'
15 |
16 | export WRAP_NEAR_TOKEN_ID=wrap.near
17 |
18 | declare -a LOCKUP_BALANCES=("5314188080644000000000" "25312260092366000000000" "17491819666921000000000" "51881732160069000000000")
19 |
20 | # use for loop to read all values and indexes
21 | for (( i=0; i<4; i++ ));
22 | do
23 | LOCKUP_BALANCE=${LOCKUP_BALANCES[$i]}
24 | LOCKUP_ACCOUNT_ID=lockup$i.$ACCOUNT_ID
25 | echo "Lockup ${LOCKUP_ACCOUNT_ID} with balance ${LOCKUP_BALANCE}"
26 | near create-account $LOCKUP_ACCOUNT_ID --masterAccount=$ACCOUNT_ID --initialBalance=20
27 | near deploy $LOCKUP_ACCOUNT_ID release/lockup$i.wasm new '{
28 | "token_account_id": "'$SKYWARD_TOKEN_ID'",
29 | "skyward_account_id": "'$CONTRACT_ID'",
30 | "claim_expiration_timestamp": 1656633600
31 | }' --initGas=200000000000000
32 | near call $SKYWARD_TOKEN_ID --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$LOCKUP_ACCOUNT_ID'"}' --amount=0.00125
33 | near call $WRAP_NEAR_TOKEN_ID --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$LOCKUP_ACCOUNT_ID'"}' --amount=0.00125
34 | near call $SKYWARD_TOKEN_ID --accountId=$ACCOUNT_ID ft_transfer '{"receiver_id": "'$LOCKUP_ACCOUNT_ID'", "amount": "'$LOCKUP_BALANCE'"}' --amount=$ONE_YOCTO
35 |
36 | near view $LOCKUP_ACCOUNT_ID get_stats
37 | done
38 |
39 | # 2021-08-01 = 1627776000
40 | # 2021-09-01 = 1630454400
41 | # 2022-01-01 = 1640995200
42 | # 2022-07-01 = 1656633600
43 |
44 | near deploy $CONTRACT_ID release/skyward.wasm new '{
45 | "skyward_token_id": "'$SKYWARD_TOKEN_ID'",
46 | "skyward_vesting_schedule": [{
47 | "start_timestamp": 1627776000,
48 | "end_timestamp": 1630454400,
49 | "amount": "10000000000000000000000"
50 | }, {
51 | "start_timestamp": 1640995200,
52 | "end_timestamp": 1656633600,
53 | "amount": "90000000000000000000000"
54 | }],
55 | "listing_fee_near": "10000000000000000000000000",
56 | "w_near_token_id": "'$WRAP_NEAR_TOKEN_ID'"
57 | }'
58 |
59 | near call $SKYWARD_TOKEN_ID --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$CONTRACT_ID'"}' --amount=0.00125
60 | near call $WRAP_NEAR_TOKEN_ID --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$CONTRACT_ID'"}' --amount=0.00125
61 |
--------------------------------------------------------------------------------
/skyward/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "skyward"
3 | version = "0.1.0"
4 | authors = ["Spensa Nightshade "]
5 | edition = "2018"
6 |
7 | [lib]
8 | crate-type = ["cdylib", "rlib"]
9 |
10 | [dependencies]
11 | near-sdk = "3.1.0"
12 | near-contract-standards = "3.1.0"
13 | uint = { version = "0.9.0", default-features = false }
14 |
15 | [dev-dependencies]
16 | near-sdk-sim = "3.2.0"
17 |
18 | [profile.release]
19 | codegen-units=1
20 | opt-level = "z"
21 | lto = true
22 | debug = false
23 | panic = "abort"
24 | overflow-checks = true
25 |
--------------------------------------------------------------------------------
/skyward/README.md:
--------------------------------------------------------------------------------
1 | ## Build and Init
2 |
3 | ```bash
4 | ./build.sh
5 | near dev-deploy res/skyward.was
6 | export CONTRACT_ID=skyward.testnet
7 | export SKYWARD_TOKEN_ID=token.skyward.testnet
8 |
9 | near call $CONTRACT_ID --accountId=$CONTRACT_ID new '{"skyward_token_id": "'$SKYWARD_TOKEN_ID'", "skyward_total_supply": "1000000000000000000000000", "listing_fee_near": "10000000000000000000000000"}'
10 | ```
11 |
12 | ## Register tokens for ACCOUNT_ID
13 |
14 | ```bash
15 | export TOKEN1=token1.testnet
16 | export TOKEN2=token2.testnet
17 | export ACCOUNT_ID=account1.testnet
18 | export ACCOUNT_ID2=account2.testnet
19 |
20 | # Init tokens
21 | # near call $TOKEN1 --accountId=$ACCOUNT_ID new_default_meta '{"owner_id": "'$ACCOUNT_ID'", "total_supply": "1000000000000000000000000000000000"}'
22 | # near call $TOKEN2 --accountId=$ACCOUNT_ID2 new_default_meta '{"owner_id": "'$ACCOUNT_ID2'", "total_supply": "1000000000000000000000000000000000"}'
23 |
24 | # Register both tokens for $ACCOUNT_ID (even though only TOKEN1) is needed now
25 | near call $CONTRACT_ID --accountId=$ACCOUNT_ID register_tokens '{"token_account_ids": ["'$TOKEN1'", "'$TOKEN2'"]}' --amount=0.01
26 |
27 | near view $CONTRACT_ID get_account_balances '{"account_id": "'$ACCOUNT_ID'"}'
28 | ```
29 |
30 | ## Register contract with tokens
31 |
32 | ```bash
33 | near call $TOKEN1 --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$CONTRACT_ID'"}' --amount=0.00125
34 | near call $TOKEN2 --accountId=$ACCOUNT_ID storage_deposit '{"account_id": "'$CONTRACT_ID'"}' --amount=0.00125
35 | ```
36 |
37 | ## Deposit TOKEN1
38 |
39 | ```bash
40 | export AMOUNT=1000000000000000000000000000000
41 | near call $TOKEN1 --accountId=$ACCOUNT_ID ft_transfer_call '{"receiver_id": "'$CONTRACT_ID'", "amount": "'$AMOUNT'", "memo": "Yolo for sale", "msg": "\"AccountDeposit\""}' --amount=0.000000000000000000000001
42 | near view $CONTRACT_ID get_account_balances '{"account_id": "'$ACCOUNT_ID'"}'
43 | ```
44 |
45 | ## Register 2nd account with contract
46 | ```bash
47 | near call $CONTRACT_ID --accountId=$ACCOUNT_ID2 register_token '{"token_account_id": "'$TOKEN2'"}' --amount=0.01
48 | ```
49 |
50 | ## Deposit TOKEN2 from 2nd account to contract
51 | ```bash
52 | export AMOUNT=1000000000000000000000000000000
53 | near call $TOKEN2 --accountId=$ACCOUNT_ID2 ft_transfer_call '{"receiver_id": "'$CONTRACT_ID'", "amount": "'$AMOUNT'", "memo": "BUY BUY BUY", "msg": "\"AccountDeposit\""}' --amount=0.000000000000000000000001
54 | near view $CONTRACT_ID get_account_balances '{"account_id": "'$ACCOUNT_ID2'"}'
55 | ```
56 |
57 | ## Creating sale
58 | ```bash
59 | # Mac + fish
60 | # echo (date -v +10M '+%s')"000000000"
61 | export START_TIMESTAMP=1600000000000000000
62 | export SALE_AMOUNT=500000000000000000000000000000
63 | near call $CONTRACT_ID --accountId=$ACCOUNT_ID sale_create '{"sale": {"out_token_account_id": "'$TOKEN1'", "out_token_balance": "'$SALE_AMOUNT'", "in_token_account_id": "'$TOKEN2'", "start_time": "'$START_TIMESTAMP'", "duration": "3600000000000"}}' --amount=0.1
64 |
65 | near view $CONTRACT_ID get_sales
66 | ````
67 |
68 | ## Joining SALE
69 |
70 | ```bash
71 | export AMOUNT=1000000000000000000000000000
72 | export SALE_ID=0
73 | near call $CONTRACT_ID --accountId=$ACCOUNT_ID2 sale_deposit_in_token '{"sale_id": '$SALE_ID', "amount": "'$AMOUNT'"}' --amount=0.01
74 |
75 | near view $CONTRACT_ID get_sales '{"account_id": "'$ACCOUNT_ID2'"}'
76 | ```
77 |
78 | ## Claiming from SALE
79 |
80 | ```bash
81 | near call $CONTRACT_ID --accountId=$ACCOUNT_ID2 sale_claim_out_tokens '{"sale_id": '$SALE_ID'}'
82 |
83 | near view $CONTRACT_ID get_sales '{"account_id": "'$ACCOUNT_ID2'"}'
84 | ```
85 |
--------------------------------------------------------------------------------
/skyward/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
6 | mkdir -p ./res
7 | cp target/wasm32-unknown-unknown/release/skyward.wasm ./res/
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/skyward/src/account.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver;
3 | use near_sdk::collections::{UnorderedMap, UnorderedSet};
4 | use near_sdk::json_types::{WrappedBalance, U128};
5 | use near_sdk::{assert_one_yocto, serde_json, PromiseOrValue};
6 |
7 | const REFERRAL_FEE_DENOMINATOR: u128 = 10000;
8 |
9 | #[derive(BorshSerialize, BorshDeserialize)]
10 | pub struct Account {
11 | pub balances: UnorderedMap,
12 | pub subs: UnorderedMap,
13 | pub sales: UnorderedSet,
14 | }
15 |
16 | #[derive(BorshDeserialize, BorshSerialize)]
17 | pub enum VAccount {
18 | Current(Account),
19 | }
20 |
21 | impl From for VAccount {
22 | fn from(account: Account) -> Self {
23 | Self::Current(account)
24 | }
25 | }
26 |
27 | impl From for Account {
28 | fn from(v_account: VAccount) -> Self {
29 | match v_account {
30 | VAccount::Current(account) => account,
31 | }
32 | }
33 | }
34 |
35 | impl Account {
36 | pub fn internal_token_deposit(&mut self, token_account_id: &TokenAccountId, amount: Balance) {
37 | let balance = self
38 | .balances
39 | .get(&token_account_id)
40 | .expect(errors::TOKEN_NOT_REGISTERED);
41 | let new_balance = balance.checked_add(amount).expect(errors::BALANCE_OVERFLOW);
42 | self.balances.insert(token_account_id, &new_balance);
43 | }
44 |
45 | pub fn internal_token_withdraw(&mut self, token_account_id: &TokenAccountId, amount: Balance) {
46 | let balance = self
47 | .balances
48 | .get(&token_account_id)
49 | .expect(errors::TOKEN_NOT_REGISTERED);
50 | let new_balance = balance
51 | .checked_sub(amount)
52 | .expect(errors::NOT_ENOUGH_BALANCE);
53 | self.balances.insert(token_account_id, &new_balance);
54 | }
55 |
56 | pub fn internal_get_subscription(
57 | &self,
58 | sale_id: u64,
59 | sale: &Sale,
60 | referral_id: Option<&AccountId>,
61 | create_new: bool,
62 | ) -> (Subscription, Vec) {
63 | let mut subscription: Subscription = self
64 | .subs
65 | .get(&sale_id)
66 | .map(|s| s.into())
67 | .unwrap_or_else(|| {
68 | if create_new {
69 | Subscription::new(sale, referral_id.cloned())
70 | } else {
71 | env::panic(errors::NO_PERMISSION.as_bytes())
72 | }
73 | });
74 | let out_token_amounts = subscription.touch(sale);
75 | (subscription, out_token_amounts)
76 | }
77 |
78 | pub fn internal_save_subscription(
79 | &mut self,
80 | sale_id: u64,
81 | sale: &Sale,
82 | subscription: Subscription,
83 | ) {
84 | if subscription.shares == 0 && (sale.permissions_contract_id.is_none() || sale.has_ended())
85 | {
86 | self.subs.remove(&sale_id);
87 | } else {
88 | self.subs.insert(&sale_id, &subscription.into());
89 | }
90 | }
91 |
92 | pub fn internal_subscription_output(
93 | &self,
94 | sale_id: u64,
95 | sale: &Sale,
96 | ) -> Option {
97 | let (subscription, out_token_remaining) =
98 | self.internal_get_subscription(sale_id, sale, None, true);
99 | if subscription.shares > 0 || out_token_remaining.iter().any(|&v| v > 0) {
100 | let remaining_in_balance = sale.shares_to_in_balance(subscription.shares);
101 | Some(SubscriptionOutput {
102 | remaining_in_balance: remaining_in_balance.into(),
103 | spent_in_balance: (subscription.spent_in_balance_without_shares
104 | + (subscription.last_in_balance - remaining_in_balance))
105 | .into(),
106 | unclaimed_out_balances: out_token_remaining.into_iter().map(|b| b.into()).collect(),
107 | claimed_out_balance: subscription
108 | .claimed_out_balance
109 | .into_iter()
110 | .map(|b| b.into())
111 | .collect(),
112 | shares: subscription.shares.into(),
113 | referral_id: subscription.referral_id,
114 | })
115 | } else {
116 | None
117 | }
118 | }
119 | }
120 |
121 | impl Contract {
122 | pub fn internal_unwrap_account(&self, account_id: &AccountId) -> Account {
123 | self.accounts
124 | .get(account_id)
125 | .expect(errors::ACCOUNT_NOT_FOUND)
126 | .into()
127 | }
128 |
129 | pub fn internal_maybe_register_token(
130 | &mut self,
131 | account: &mut Account,
132 | token_account_id: &TokenAccountId,
133 | ) {
134 | if account.balances.get(token_account_id).is_none() {
135 | account.balances.insert(token_account_id, &0);
136 | if token_account_id != &self.treasury.skyward_token_id {
137 | self.treasury.internal_deposit(token_account_id, 0);
138 | }
139 | }
140 | }
141 |
142 | pub fn internal_update_subscription(
143 | &mut self,
144 | account: &mut Account,
145 | sale_id: u64,
146 | sale: &mut Sale,
147 | referral_id: Option<&AccountId>,
148 | passed_permission_check: bool,
149 | ) -> Subscription {
150 | let create_new = passed_permission_check || sale.permissions_contract_id.is_none();
151 | let (mut subscription, out_token_amounts) =
152 | account.internal_get_subscription(sale_id, &sale, referral_id, create_new);
153 | for (index, (mut amount, out_token)) in out_token_amounts
154 | .into_iter()
155 | .zip(sale.out_tokens.iter())
156 | .enumerate()
157 | {
158 | if amount > 0 {
159 | if let Some(referral_bpt) = out_token.referral_bpt {
160 | let mut ref_amount = (U256::from(amount) * U256::from(referral_bpt)
161 | / U256::from(REFERRAL_FEE_DENOMINATOR))
162 | .as_u128();
163 | let referral_id = subscription
164 | .referral_id
165 | .as_ref()
166 | .map(|referral_id| {
167 | ref_amount /= 2;
168 | referral_id
169 | })
170 | .unwrap_or(&sale.owner_id);
171 | if ref_amount > 0 {
172 | amount -= ref_amount;
173 | if let Some(referral) = self.accounts.get(referral_id) {
174 | let mut referral: Account = referral.into();
175 | if referral.balances.get(&out_token.token_account_id).is_some() {
176 | referral.internal_token_deposit(
177 | &out_token.token_account_id,
178 | ref_amount,
179 | );
180 | ref_amount = 0;
181 | self.accounts.insert(referral_id, &referral.into());
182 | }
183 | }
184 | if ref_amount > 0 {
185 | self.treasury
186 | .internal_donate(&out_token.token_account_id, ref_amount);
187 | }
188 | }
189 | }
190 | account.internal_token_deposit(&out_token.token_account_id, amount);
191 | subscription.claimed_out_balance[index] += amount;
192 | }
193 | }
194 | if subscription.shares > 0 {
195 | let remaining_in_amount = sale.shares_to_in_balance(subscription.shares);
196 | if remaining_in_amount == 0 {
197 | sale.total_shares -= subscription.shares;
198 | subscription.shares = 0;
199 | }
200 | }
201 | subscription
202 | }
203 | }
204 |
205 | #[near_bindgen]
206 | impl Contract {
207 | #[payable]
208 | pub fn register_token(
209 | &mut self,
210 | account_id: Option,
211 | token_account_id: ValidAccountId,
212 | ) {
213 | self.register_tokens(account_id, vec![token_account_id])
214 | }
215 |
216 | #[payable]
217 | pub fn register_tokens(
218 | &mut self,
219 | account_id: Option,
220 | token_account_ids: Vec,
221 | ) {
222 | assert_at_least_one_yocto();
223 | let initial_storage_usage = env::storage_usage();
224 | let account_id = account_id
225 | .map(|a| a.into())
226 | .unwrap_or_else(env::predecessor_account_id);
227 | let mut account = self
228 | .accounts
229 | .get(&account_id)
230 | .map(|a| a.into())
231 | .unwrap_or_else(|| Account {
232 | balances: UnorderedMap::new(StorageKey::AccountTokens {
233 | account_id: account_id.clone(),
234 | }),
235 | subs: UnorderedMap::new(StorageKey::AccountSubs {
236 | account_id: account_id.clone(),
237 | }),
238 | sales: UnorderedSet::new(StorageKey::AccountSales {
239 | account_id: account_id.clone(),
240 | }),
241 | });
242 | for token_account_id in token_account_ids {
243 | self.internal_maybe_register_token(&mut account, token_account_id.as_ref());
244 | }
245 | self.accounts.insert(&account_id, &account.into());
246 | refund_extra_storage_deposit(env::storage_usage() - initial_storage_usage, 0);
247 | }
248 |
249 | pub fn withdraw_token(
250 | &mut self,
251 | token_account_id: ValidAccountId,
252 | amount: Option,
253 | ) -> Promise {
254 | let account_id = env::predecessor_account_id();
255 | let mut account = self.internal_unwrap_account(&account_id);
256 | let amount = amount.map(|a| a.0).unwrap_or_else(|| {
257 | account
258 | .balances
259 | .get(token_account_id.as_ref())
260 | .expect(errors::TOKEN_NOT_REGISTERED)
261 | });
262 | account.internal_token_withdraw(token_account_id.as_ref(), amount);
263 | self.internal_ft_transfer(&account_id, token_account_id.as_ref(), amount)
264 | }
265 |
266 | #[payable]
267 | pub fn donate_token_to_treasury(
268 | &mut self,
269 | token_account_id: ValidAccountId,
270 | amount: WrappedBalance,
271 | ) {
272 | assert_one_yocto();
273 | let account_id = env::predecessor_account_id();
274 | let mut account = self.internal_unwrap_account(&account_id);
275 | account.internal_token_withdraw(token_account_id.as_ref(), amount.0);
276 | self.treasury
277 | .internal_donate(token_account_id.as_ref(), amount.0);
278 | }
279 |
280 | pub fn balance_of(
281 | &self,
282 | account_id: ValidAccountId,
283 | token_account_id: ValidAccountId,
284 | ) -> Option {
285 | self.accounts.get(account_id.as_ref()).and_then(|account| {
286 | let account: Account = account.into();
287 | account
288 | .balances
289 | .get(token_account_id.as_ref())
290 | .map(|a| a.into())
291 | })
292 | }
293 |
294 | pub fn balances_of(
295 | &self,
296 | account_id: ValidAccountId,
297 | from_index: Option,
298 | limit: Option,
299 | ) -> Vec<(TokenAccountId, WrappedBalance)> {
300 | if let Some(account) = self.accounts.get(account_id.as_ref()) {
301 | let account: Account = account.into();
302 | let keys = account.balances.keys_as_vector();
303 | let values = account.balances.values_as_vector();
304 | let from_index = from_index.unwrap_or(0);
305 | let limit = limit.unwrap_or(keys.len());
306 | (from_index..std::cmp::min(from_index + limit, keys.len()))
307 | .map(|index| (keys.get(index).unwrap(), values.get(index).unwrap().into()))
308 | .collect()
309 | } else {
310 | vec![]
311 | }
312 | }
313 |
314 | pub fn get_num_balances(&self, account_id: ValidAccountId) -> u64 {
315 | self.accounts
316 | .get(account_id.as_ref())
317 | .map(|account| {
318 | let account: Account = account.into();
319 | account.balances.len()
320 | })
321 | .unwrap_or(0)
322 | }
323 |
324 | pub fn get_subscribed_sales(
325 | &self,
326 | account_id: ValidAccountId,
327 | from_index: Option,
328 | limit: Option,
329 | ) -> Vec {
330 | if let Some(account) = self.accounts.get(account_id.as_ref()) {
331 | let account: Account = account.into();
332 | let keys = account.subs.keys_as_vector();
333 | let from_index = from_index.unwrap_or(0);
334 | let limit = limit.unwrap_or(keys.len());
335 | (from_index..std::cmp::min(from_index + limit, keys.len()))
336 | .filter_map(|index| {
337 | let sale_id = keys.get(index).unwrap();
338 | self.internal_get_sale(sale_id, Some(&account))
339 | })
340 | .collect()
341 | } else {
342 | vec![]
343 | }
344 | }
345 |
346 | pub fn get_account_sales(
347 | &self,
348 | account_id: ValidAccountId,
349 | from_index: Option,
350 | limit: Option,
351 | ) -> Vec {
352 | if let Some(account) = self.accounts.get(account_id.as_ref()) {
353 | let account: Account = account.into();
354 | let keys = account.sales.as_vector();
355 | let from_index = from_index.unwrap_or(0);
356 | let limit = limit.unwrap_or(keys.len());
357 | (from_index..std::cmp::min(from_index + limit, keys.len()))
358 | .filter_map(|index| {
359 | let sale_id = keys.get(index).unwrap();
360 | self.internal_get_sale(sale_id, Some(&account))
361 | })
362 | .collect()
363 | } else {
364 | vec![]
365 | }
366 | }
367 | }
368 |
369 | #[near_bindgen]
370 | impl FungibleTokenReceiver for Contract {
371 | fn ft_on_transfer(
372 | &mut self,
373 | sender_id: ValidAccountId,
374 | amount: U128,
375 | msg: String,
376 | ) -> PromiseOrValue {
377 | let args: FtOnTransferArgs =
378 | serde_json::from_str(&msg).expect(errors::FAILED_TO_PARSE_FT_ON_TRANSFER_MSG);
379 | let token_account_id = env::predecessor_account_id();
380 | match args {
381 | FtOnTransferArgs::AccountDeposit => {
382 | let mut account = self.internal_unwrap_account(sender_id.as_ref());
383 | account.internal_token_deposit(&token_account_id, amount.0);
384 | }
385 | FtOnTransferArgs::DonateToTreasury => {
386 | let initial_storage_usage = env::storage_usage();
387 | self.treasury.internal_donate(&token_account_id, amount.0);
388 | assert_eq!(
389 | initial_storage_usage,
390 | env::storage_usage(),
391 | "{}",
392 | errors::UNREGISTERED_TREASURY_TOKEN
393 | );
394 | }
395 | }
396 | PromiseOrValue::Value(0.into())
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/skyward/src/errors.rs:
--------------------------------------------------------------------------------
1 | pub(crate) const STARTS_TOO_SOON: &str = "ERR_STARTS_TOO_SOON";
2 | pub(crate) const MAX_DURATION_TO_START: &str = "ERR_MAX_DURATION_TO_START";
3 | pub(crate) const MAX_DURATION: &str = "ERR_MAX_DURATION";
4 | pub(crate) const MIN_DURATION: &str = "ERR_MIN_DURATION";
5 | pub(crate) const SALE_NOT_FOUND: &str = "ERR_SALE_NOT_FOUND";
6 | pub(crate) const SALE_ENDED: &str = "ERR_SALE_ENDED";
7 | pub(crate) const SHARES_OVERFLOW: &str = "ERR_SHARES_OVERFLOW";
8 | pub(crate) const ACCOUNT_NOT_FOUND: &str = "ERR_ACCOUNT_NOT_FOUND";
9 | pub(crate) const NOT_ENOUGH_BALANCE: &str = "ERR_NOT_ENOUGH_BALANCE";
10 | pub(crate) const ZERO_IN_AMOUNT: &str = "ERR_ZERO_IN_AMOUNT";
11 | pub(crate) const ZERO_OUT_AMOUNT: &str = "ERR_ZERO_OUT_AMOUNT";
12 | pub(crate) const NOT_ENOUGH_SHARES: &str = "ERR_NOT_ENOUGH_SHARES";
13 | pub(crate) const ZERO_SHARES: &str = "ERR_ZERO_SHARES";
14 | pub(crate) const ZERO_SKYWARD: &str = "ERR_ZERO_SKYWARD";
15 | pub(crate) const BALANCE_OVERFLOW: &str = "ERR_BALANCE_OVERFLOW";
16 | pub(crate) const TOKEN_NOT_REGISTERED: &str = "ERR_TOKEN_NOT_REGISTERED";
17 | pub(crate) const NOT_ENOUGH_ATTACHED_BALANCE: &str = "ERR_NOT_ENOUGH_ATTACHED_BALANCE";
18 | pub(crate) const FAILED_TO_PARSE_FT_ON_TRANSFER_MSG: &str =
19 | "ERR_FAILED_TO_PARSE_FT_ON_TRANSFER_MSG";
20 | pub(crate) const NEED_AT_LEAST_ONE_YOCTO: &str = "ERR_NEED_AT_LEAST_ONE_YOCTO";
21 | pub(crate) const TOKEN_WITHDRAW_FAILED: &str = "ERR_TOKEN_WITHDRAW_FAILED";
22 | pub(crate) const SAME_TOKENS: &str = "ERR_SAME_TOKENS";
23 | pub(crate) const TREASURY_CAN_NOT_CONTAIN_SKYWARD: &str = "ERR_TREASURY_CAN_NOT_CONTAIN_SKYWARD";
24 | pub(crate) const NON_UNIQUE_OUT_TOKENS: &str = "ERR_NON_UNIQUE_OUT_TOKENS";
25 | pub(crate) const MAX_NUM_OUT_TOKENS: &str = "ERR_MAX_NUM_OUT_TOKENS";
26 | pub(crate) const SELF_REFERRAL: &str = "ERR_SELF_REFERRAL";
27 | pub(crate) const UNREGISTERED_TREASURY_TOKEN: &str = "ERR_UNREGISTERED_TREASURY_TOKEN";
28 | pub(crate) const INVALID_INITIAL_SKYWARD_SALE: &str = "ERR_INVALID_INITIAL_SKYWARD_SALE";
29 | pub(crate) const TOO_LONG_TITLE: &str = "ERR_TOO_LONG_TITLE";
30 | pub(crate) const TOO_LONG_URL: &str = "ERR_TOO_LONG_URL";
31 | pub(crate) const NO_PERMISSION: &str = "ERR_NO_PERMISSION";
32 | pub(crate) const NOT_APPROVED: &str = "ERR_NOT_APPROVED";
33 | pub(crate) const MAX_REFERRAL_BPT: &str = "ERR_MAX_REFERRAL_BPT";
34 |
--------------------------------------------------------------------------------
/skyward/src/internal.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_contract_standards::fungible_token::core_impl::ext_fungible_token;
3 | use near_sdk::is_promise_success;
4 | use near_sdk::json_types::WrappedBalance;
5 |
6 | #[derive(Serialize, Deserialize)]
7 | #[serde(crate = "near_sdk::serde")]
8 | pub enum FtOnTransferArgs {
9 | AccountDeposit,
10 | DonateToTreasury,
11 | }
12 |
13 | #[ext_contract(ext_permission_contract)]
14 | trait ExtPermissionContract {
15 | fn is_approved(&mut self, account_id: AccountId, sale_id: u64);
16 | }
17 |
18 | #[ext_contract(ext_self)]
19 | trait SelfCallbacks {
20 | fn after_ft_transfer(
21 | &mut self,
22 | account_id: AccountId,
23 | token_account_id: AccountId,
24 | amount: WrappedBalance,
25 | ) -> bool;
26 |
27 | fn after_near_deposit(&mut self, amount: WrappedBalance) -> bool;
28 |
29 | fn after_is_approved(
30 | &mut self,
31 | sale_id: u64,
32 | account_id: AccountId,
33 | in_amount: WrappedBalance,
34 | referral_id: Option,
35 | attached_deposit: WrappedBalance,
36 | );
37 |
38 | fn maybe_refund_deposit(
39 | &mut self,
40 | account_id: AccountId,
41 | attached_deposit: WrappedBalance,
42 | ) -> bool;
43 | }
44 |
45 | trait SelfCallbacks {
46 | fn after_ft_transfer(
47 | &mut self,
48 | account_id: AccountId,
49 | token_account_id: AccountId,
50 | amount: WrappedBalance,
51 | ) -> bool;
52 |
53 | fn after_near_deposit(&mut self, amount: WrappedBalance) -> bool;
54 |
55 | fn after_is_approved(
56 | &mut self,
57 | is_approved: bool,
58 | sale_id: u64,
59 | account_id: AccountId,
60 | in_amount: WrappedBalance,
61 | referral_id: Option,
62 | attached_deposit: WrappedBalance,
63 | );
64 |
65 | fn maybe_refund_deposit(
66 | &mut self,
67 | account_id: AccountId,
68 | attached_deposit: WrappedBalance,
69 | ) -> bool;
70 | }
71 |
72 | impl Contract {
73 | pub fn internal_ft_transfer(
74 | &mut self,
75 | account_id: &AccountId,
76 | token_account_id: &AccountId,
77 | amount: Balance,
78 | ) -> Promise {
79 | ext_fungible_token::ft_transfer(
80 | account_id.clone(),
81 | amount.into(),
82 | None,
83 | &token_account_id,
84 | ONE_YOCTO,
85 | FT_TRANSFER_GAS,
86 | )
87 | .then(ext_self::after_ft_transfer(
88 | account_id.clone(),
89 | token_account_id.clone(),
90 | amount.into(),
91 | &env::current_account_id(),
92 | NO_DEPOSIT,
93 | AFTER_FT_TRANSFER_GAS,
94 | ))
95 | }
96 | }
97 |
98 | #[near_bindgen]
99 | impl SelfCallbacks for Contract {
100 | #[private]
101 | fn after_ft_transfer(
102 | &mut self,
103 | account_id: AccountId,
104 | token_account_id: AccountId,
105 | amount: WrappedBalance,
106 | ) -> bool {
107 | let promise_success = is_promise_success();
108 | if !is_promise_success() {
109 | log!(
110 | "{} by {} token {} amount {}",
111 | errors::TOKEN_WITHDRAW_FAILED,
112 | account_id,
113 | token_account_id,
114 | amount.0
115 | );
116 | let mut account = self.internal_unwrap_account(&account_id);
117 | account.internal_token_deposit(&token_account_id, amount.0);
118 | }
119 | promise_success
120 | }
121 |
122 | #[private]
123 | fn after_near_deposit(&mut self, amount: WrappedBalance) -> bool {
124 | let promise_success = is_promise_success();
125 | if promise_success {
126 | log!(
127 | "Successfully wrapped {} NEAR tokens into Treasury",
128 | amount.0,
129 | );
130 | let w_near_token_id = self.treasury.w_near_token_id.clone();
131 | self.treasury.internal_deposit(&w_near_token_id, amount.0);
132 | }
133 | promise_success
134 | }
135 |
136 | #[private]
137 | fn after_is_approved(
138 | &mut self,
139 | #[callback] is_approved: bool,
140 | sale_id: u64,
141 | account_id: AccountId,
142 | in_amount: WrappedBalance,
143 | referral_id: Option,
144 | attached_deposit: WrappedBalance,
145 | ) {
146 | assert!(is_approved, "{}", errors::NOT_APPROVED);
147 | let initial_storage_usage = env::storage_usage();
148 |
149 | assert!(self
150 | .internal_deposit_in_amount(
151 | sale_id,
152 | &account_id,
153 | in_amount.0,
154 | referral_id.as_ref(),
155 | true,
156 | )
157 | .is_none());
158 |
159 | let attached_deposit = attached_deposit.0;
160 | let required_cost =
161 | env::storage_byte_cost() * Balance::from(env::storage_usage() - initial_storage_usage);
162 | assert!(
163 | required_cost <= attached_deposit,
164 | "{} {}",
165 | errors::NOT_ENOUGH_ATTACHED_BALANCE,
166 | required_cost,
167 | );
168 |
169 | let refund = attached_deposit - required_cost;
170 | if refund > 1 {
171 | Promise::new(account_id).transfer(refund);
172 | }
173 | self.treasury.locked_attached_deposits -= attached_deposit;
174 | }
175 |
176 | #[private]
177 | fn maybe_refund_deposit(
178 | &mut self,
179 | account_id: AccountId,
180 | attached_deposit: WrappedBalance,
181 | ) -> bool {
182 | let promise_success = is_promise_success();
183 | if !promise_success {
184 | self.treasury.locked_attached_deposits -= attached_deposit.0;
185 | Promise::new(account_id).transfer(attached_deposit.0);
186 | }
187 | promise_success
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/skyward/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod account;
2 | pub(crate) mod errors;
3 | mod internal;
4 | pub mod sale;
5 | pub mod sub;
6 | pub mod treasury;
7 | pub(crate) mod utils;
8 |
9 | pub use crate::account::*;
10 | pub use crate::internal::*;
11 | pub use crate::sale::*;
12 | pub use crate::sub::*;
13 | pub use crate::treasury::*;
14 | pub(crate) use crate::utils::*;
15 |
16 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
17 | use near_sdk::collections::LookupMap;
18 | use near_sdk::json_types::{ValidAccountId, WrappedBalance};
19 | use near_sdk::serde::{Deserialize, Serialize};
20 | use near_sdk::{
21 | env, ext_contract, log, near_bindgen, AccountId, Balance, BorshStorageKey, PanicOnDefault,
22 | Promise, StorageUsage,
23 | };
24 |
25 | near_sdk::setup_alloc!();
26 |
27 | #[derive(BorshStorageKey, BorshSerialize)]
28 | pub(crate) enum StorageKey {
29 | Accounts,
30 | AccountTokens { account_id: AccountId },
31 | AccountSubs { account_id: AccountId },
32 | AccountSales { account_id: AccountId },
33 | Sales,
34 | TreasuryBalances,
35 | VestingSchedule,
36 | }
37 |
38 | #[near_bindgen]
39 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
40 | pub struct Contract {
41 | pub accounts: LookupMap,
42 |
43 | pub sales: LookupMap,
44 |
45 | pub num_sales: u64,
46 |
47 | pub treasury: Treasury,
48 | }
49 |
50 | #[near_bindgen]
51 | impl Contract {
52 | #[init]
53 | pub fn new(
54 | skyward_token_id: ValidAccountId,
55 | skyward_vesting_schedule: Vec,
56 | listing_fee_near: WrappedBalance,
57 | w_near_token_id: ValidAccountId,
58 | ) -> Self {
59 | Self {
60 | accounts: LookupMap::new(StorageKey::Accounts),
61 | sales: LookupMap::new(StorageKey::Sales),
62 | num_sales: 0,
63 | treasury: Treasury::new(
64 | skyward_token_id.into(),
65 | skyward_vesting_schedule,
66 | listing_fee_near.0,
67 | w_near_token_id.into(),
68 | ),
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/skyward/src/sale.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::json_types::{WrappedBalance, WrappedDuration, WrappedTimestamp};
3 | use near_sdk::{assert_one_yocto, BlockHeight, Duration, Timestamp};
4 |
5 | const MIN_DURATION_BEFORE_START: Duration = 7 * 24 * 60 * 60 * 1_000_000_000;
6 | const MAX_DURATION_BEFORE_START: Duration = 365 * 24 * 60 * 60 * 1_000_000_000;
7 | const MAX_DURATION: Duration = 4 * 366 * 24 * 60 * 60 * 1_000_000_000;
8 | /// Minimum duration. Use 1 nanosecond to run a simple auction.
9 | const MIN_DURATION: Duration = 1;
10 |
11 | pub(crate) const MULTIPLIER: u128 = 10u128.pow(38);
12 | pub(crate) const TREASURY_FEE_DENOMINATOR: Balance = 100;
13 | pub(crate) const MAX_NUM_OUT_TOKENS: usize = 4;
14 | pub(crate) const MAX_TITLE_LENGTH: usize = 250;
15 | pub(crate) const MAX_URL_LENGTH: usize = 250;
16 | pub(crate) const MAX_REFERRAL_BPT: u16 = 500;
17 |
18 | #[derive(BorshSerialize, BorshDeserialize)]
19 | pub struct OldSale {
20 | pub owner_id: AccountId,
21 |
22 | pub title: String,
23 | pub url: Option,
24 | pub permissions_contract_id: Option,
25 |
26 | pub out_tokens: Vec,
27 |
28 | pub in_token_account_id: AccountId,
29 | pub in_token_remaining: Balance,
30 | pub in_token_paid_unclaimed: Balance,
31 | pub in_token_paid: Balance,
32 |
33 | pub start_time: Timestamp,
34 | pub duration: Duration,
35 |
36 | pub total_shares: Balance,
37 | pub last_timestamp: Timestamp,
38 | }
39 |
40 | #[derive(BorshSerialize, BorshDeserialize)]
41 | #[borsh_init(touch)]
42 | pub struct Sale {
43 | pub owner_id: AccountId,
44 |
45 | pub title: String,
46 | pub url: Option,
47 | pub permissions_contract_id: Option,
48 |
49 | pub out_tokens: Vec,
50 |
51 | pub in_token_account_id: AccountId,
52 | pub in_token_remaining: Balance,
53 | pub in_token_paid_unclaimed: Balance,
54 | pub in_token_paid: Balance,
55 |
56 | pub start_time: Timestamp,
57 | pub duration: Duration,
58 |
59 | pub total_shares: Balance,
60 | pub last_timestamp: Timestamp,
61 |
62 | pub start_block_height: BlockHeight,
63 | pub end_block_height: Option,
64 | }
65 |
66 | #[derive(BorshSerialize, BorshDeserialize, Clone)]
67 | pub struct SaleOutToken {
68 | pub token_account_id: TokenAccountId,
69 | pub remaining: Balance,
70 | pub distributed: Balance,
71 | pub treasury_unclaimed: Option,
72 | pub per_share: InnerU256,
73 | pub referral_bpt: Option,
74 | }
75 |
76 | #[derive(BorshDeserialize, BorshSerialize)]
77 | pub enum VSale {
78 | First(OldSale),
79 | Current(Sale),
80 | }
81 |
82 | impl From for VSale {
83 | fn from(sale: Sale) -> Self {
84 | Self::Current(sale)
85 | }
86 | }
87 |
88 | impl From for Sale {
89 | fn from(v_sale: VSale) -> Self {
90 | match v_sale {
91 | VSale::First(old_sale) => {
92 | let mut sale = Sale {
93 | owner_id: old_sale.owner_id,
94 | title: old_sale.title,
95 | url: old_sale.url,
96 | permissions_contract_id: old_sale.permissions_contract_id,
97 | out_tokens: old_sale.out_tokens,
98 | in_token_account_id: old_sale.in_token_account_id,
99 | in_token_remaining: old_sale.in_token_remaining,
100 | in_token_paid_unclaimed: old_sale.in_token_paid_unclaimed,
101 | in_token_paid: old_sale.in_token_paid,
102 | start_time: old_sale.start_time,
103 | duration: old_sale.duration,
104 | total_shares: old_sale.total_shares,
105 | last_timestamp: old_sale.last_timestamp,
106 | start_block_height: 0,
107 | end_block_height: None,
108 | };
109 | sale.touch();
110 | sale
111 | }
112 | VSale::Current(sale) => sale,
113 | }
114 | }
115 | }
116 |
117 | #[derive(Serialize, Deserialize)]
118 | #[serde(crate = "near_sdk::serde")]
119 | pub struct SaleInput {
120 | pub title: String,
121 | pub url: Option,
122 | pub permissions_contract_id: Option,
123 |
124 | pub out_tokens: Vec,
125 |
126 | pub in_token_account_id: ValidAccountId,
127 |
128 | pub start_time: WrappedTimestamp,
129 | pub duration: WrappedDuration,
130 | }
131 |
132 | #[derive(Serialize, Deserialize)]
133 | #[serde(crate = "near_sdk::serde")]
134 | pub struct SaleInputOutToken {
135 | pub token_account_id: ValidAccountId,
136 | pub balance: WrappedBalance,
137 | pub referral_bpt: Option,
138 | }
139 |
140 | impl SaleOutToken {
141 | pub fn from_input(token: SaleInputOutToken, skyward_token_id: &TokenAccountId) -> Self {
142 | let is_skyward_token = token.token_account_id.as_ref() == skyward_token_id;
143 | Self {
144 | token_account_id: token.token_account_id.into(),
145 | remaining: token.balance.into(),
146 | distributed: 0,
147 | treasury_unclaimed: if is_skyward_token { None } else { Some(0) },
148 | per_share: U256::zero().0,
149 | referral_bpt: token.referral_bpt,
150 | }
151 | }
152 | }
153 |
154 | #[derive(Serialize, Deserialize)]
155 | #[serde(crate = "near_sdk::serde")]
156 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))]
157 | pub struct SaleOutput {
158 | pub sale_id: u64,
159 |
160 | pub title: String,
161 | pub url: Option,
162 | pub permissions_contract_id: Option,
163 |
164 | pub owner_id: AccountId,
165 |
166 | pub out_tokens: Vec,
167 |
168 | pub in_token_account_id: AccountId,
169 | pub in_token_remaining: WrappedBalance,
170 | pub in_token_paid_unclaimed: WrappedBalance,
171 | pub in_token_paid: WrappedBalance,
172 |
173 | pub total_shares: WrappedBalance,
174 |
175 | pub start_time: WrappedTimestamp,
176 | pub duration: WrappedDuration,
177 | pub remaining_duration: WrappedDuration,
178 |
179 | pub subscription: Option,
180 |
181 | pub current_time: WrappedTimestamp,
182 | pub current_block_height: BlockHeight,
183 | pub start_block_height: BlockHeight,
184 | pub end_block_height: Option,
185 | }
186 |
187 | #[derive(Serialize, Deserialize)]
188 | #[serde(crate = "near_sdk::serde")]
189 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))]
190 | pub struct SaleOutputOutToken {
191 | pub token_account_id: TokenAccountId,
192 | pub remaining: WrappedBalance,
193 | pub distributed: WrappedBalance,
194 | pub treasury_unclaimed: Option,
195 | pub referral_bpt: Option,
196 | }
197 |
198 | impl From for SaleOutputOutToken {
199 | fn from(token: SaleOutToken) -> Self {
200 | Self {
201 | token_account_id: token.token_account_id,
202 | remaining: token.remaining.into(),
203 | distributed: token.distributed.into(),
204 | treasury_unclaimed: token.treasury_unclaimed.map(|b| b.into()),
205 | referral_bpt: token.referral_bpt,
206 | }
207 | }
208 | }
209 |
210 | impl Sale {
211 | pub fn touch(&mut self) {
212 | let end_time = self.start_time + self.duration;
213 | let timestamp = std::cmp::min(end_time, env::block_timestamp());
214 | if timestamp <= self.last_timestamp {
215 | // Sale haven't started or already updated.
216 | return;
217 | }
218 | if self.last_timestamp >= end_time {
219 | // Sale closed
220 | return;
221 | }
222 | if timestamp >= end_time {
223 | self.end_block_height = Some(env::block_index());
224 | }
225 | if self.total_shares == 0 {
226 | self.last_timestamp = timestamp;
227 | return;
228 | }
229 | let time_diff = U256::from(timestamp - self.last_timestamp);
230 | let remaining_duration = U256::from(end_time - self.last_timestamp);
231 |
232 | for out_token in &mut self.out_tokens {
233 | let mut amount =
234 | (U256::from(out_token.remaining) * time_diff / remaining_duration).as_u128();
235 | if amount > 0 {
236 | out_token.distributed += amount;
237 | out_token.remaining -= amount;
238 | if let Some(treasury_unclaimed) = &mut out_token.treasury_unclaimed {
239 | let treasury_fee = amount / TREASURY_FEE_DENOMINATOR;
240 | *treasury_unclaimed += treasury_fee;
241 | amount -= treasury_fee;
242 | }
243 | out_token.per_share = (U256(out_token.per_share)
244 | + U256::from(amount) * U256::from(MULTIPLIER) / U256::from(self.total_shares))
245 | .0;
246 | }
247 | }
248 |
249 | let in_token_amount =
250 | (U256::from(self.in_token_remaining) * time_diff / remaining_duration).as_u128();
251 | self.in_token_paid_unclaimed += in_token_amount;
252 | self.in_token_paid += in_token_amount;
253 | self.in_token_remaining -= in_token_amount;
254 |
255 | self.last_timestamp = timestamp;
256 | }
257 |
258 | pub fn assert_valid_not_started(&self) {
259 | let timestamp = env::block_timestamp();
260 | assert!(
261 | &self.owner_id == &env::current_account_id()
262 | || self.start_time >= timestamp + MIN_DURATION_BEFORE_START,
263 | "{}",
264 | errors::STARTS_TOO_SOON
265 | );
266 | assert!(
267 | self.start_time < timestamp + MAX_DURATION_BEFORE_START,
268 | "{}",
269 | errors::MAX_DURATION_TO_START
270 | );
271 | assert!(self.duration <= MAX_DURATION, "{}", errors::MAX_DURATION);
272 | assert!(self.duration >= MIN_DURATION, "{}", errors::MIN_DURATION);
273 | assert!(
274 | self.out_tokens.len() <= MAX_NUM_OUT_TOKENS,
275 | "{}",
276 | errors::MAX_NUM_OUT_TOKENS
277 | );
278 | assert!(
279 | self.title.len() <= MAX_TITLE_LENGTH,
280 | "{}",
281 | errors::TOO_LONG_TITLE
282 | );
283 | assert!(
284 | self.url.as_ref().map(|s| s.len()).unwrap_or(0) <= MAX_URL_LENGTH,
285 | "{}",
286 | errors::TOO_LONG_URL
287 | );
288 |
289 | let mut unique_tokens = Vec::with_capacity(self.out_tokens.len());
290 | for out_token in &self.out_tokens {
291 | assert!(out_token.remaining > 0, "{}", errors::ZERO_OUT_AMOUNT);
292 | assert_ne!(
293 | self.in_token_account_id,
294 | out_token.token_account_id,
295 | "{}",
296 | errors::SAME_TOKENS
297 | );
298 | if let Some(referral_bpt) = out_token.referral_bpt {
299 | assert!(
300 | referral_bpt <= MAX_REFERRAL_BPT,
301 | "{}",
302 | errors::MAX_REFERRAL_BPT
303 | );
304 | }
305 | unique_tokens.push(out_token.token_account_id.clone());
306 | }
307 | unique_tokens.sort();
308 | unique_tokens.dedup();
309 | assert_eq!(
310 | unique_tokens.len(),
311 | self.out_tokens.len(),
312 | "{}",
313 | errors::NON_UNIQUE_OUT_TOKENS
314 | );
315 | }
316 |
317 | pub fn from_input(
318 | sale: SaleInput,
319 | owner_id: AccountId,
320 | skyward_token_id: &TokenAccountId,
321 | ) -> Self {
322 | let start_time = sale.start_time.into();
323 | Sale {
324 | owner_id,
325 | title: sale.title,
326 | url: sale.url,
327 | permissions_contract_id: sale.permissions_contract_id.map(|a| a.into()),
328 | out_tokens: sale
329 | .out_tokens
330 | .into_iter()
331 | .map(|o| SaleOutToken::from_input(o, skyward_token_id))
332 | .collect(),
333 | in_token_account_id: sale.in_token_account_id.into(),
334 | in_token_remaining: 0,
335 | in_token_paid_unclaimed: 0,
336 | in_token_paid: 0,
337 | total_shares: 0,
338 | start_time,
339 | duration: sale.duration.into(),
340 | last_timestamp: start_time,
341 | start_block_height: env::block_index(),
342 | end_block_height: None,
343 | }
344 | }
345 |
346 | pub fn into_output(self, sale_id: u64, account: Option<&Account>) -> SaleOutput {
347 | let remaining_duration = self.start_time + self.duration - self.last_timestamp;
348 | let subscription =
349 | account.and_then(|account| account.internal_subscription_output(sale_id, &self));
350 | SaleOutput {
351 | sale_id,
352 | owner_id: self.owner_id,
353 | title: self.title,
354 | url: self.url,
355 | permissions_contract_id: self.permissions_contract_id,
356 | out_tokens: self.out_tokens.into_iter().map(|o| o.into()).collect(),
357 | in_token_account_id: self.in_token_account_id,
358 | in_token_remaining: self.in_token_remaining.into(),
359 | in_token_paid_unclaimed: self.in_token_paid_unclaimed.into(),
360 | in_token_paid: self.in_token_paid.into(),
361 | total_shares: self.total_shares.into(),
362 | start_time: self.start_time.into(),
363 | duration: self.duration.into(),
364 | remaining_duration: remaining_duration.into(),
365 | subscription,
366 | current_time: env::block_timestamp().into(),
367 | current_block_height: env::block_index(),
368 | start_block_height: self.start_block_height,
369 | end_block_height: self.end_block_height,
370 | }
371 | }
372 |
373 | /// Returns remaining in_balance
374 | pub fn shares_to_in_balance(&self, shares: Balance) -> Balance {
375 | if shares == 0 {
376 | return 0;
377 | }
378 | return (U256::from(self.in_token_remaining) * U256::from(shares)
379 | / U256::from(self.total_shares))
380 | .as_u128();
381 | }
382 |
383 | pub fn in_amount_to_shares(&self, in_amount: Balance, round_up: bool) -> Balance {
384 | if self.total_shares == 0 {
385 | return in_amount;
386 | }
387 | assert!(
388 | self.in_token_remaining > 0 && !self.has_ended(),
389 | "{}",
390 | errors::SALE_ENDED
391 | );
392 | let in_token_remaining = U256::from(self.in_token_remaining);
393 | let num_shares = U256::from(in_amount) * U256::from(self.total_shares);
394 | let num_shares = if round_up {
395 | (num_shares + in_token_remaining - 1) / in_token_remaining
396 | } else {
397 | num_shares / in_token_remaining
398 | };
399 | if !round_up {
400 | assert!(
401 | num_shares + U256::from(self.total_shares) < U256::from(u128::MAX),
402 | "{}",
403 | errors::SHARES_OVERFLOW
404 | );
405 | }
406 | num_shares.as_u128()
407 | }
408 |
409 | pub fn has_ended(&self) -> bool {
410 | self.last_timestamp >= self.start_time + self.duration
411 | }
412 | }
413 |
414 | impl Contract {
415 | pub fn internal_unwrap_sale(&self, sale_id: u64) -> Sale {
416 | self.sales
417 | .get(&sale_id)
418 | .expect(errors::SALE_NOT_FOUND)
419 | .into()
420 | }
421 |
422 | pub fn internal_get_sale(&self, sale_id: u64, account: Option<&Account>) -> Option {
423 | self.sales.get(&sale_id).map(|v_sale| {
424 | let sale: Sale = v_sale.into();
425 | sale.into_output(sale_id, account)
426 | })
427 | }
428 |
429 | pub fn internal_distribute_unclaimed_tokens(&mut self, sale: &mut Sale) {
430 | if sale.in_token_paid_unclaimed > 0 {
431 | if &sale.owner_id == &env::current_account_id() {
432 | // Skyward Sale
433 | self.treasury
434 | .internal_donate(&sale.in_token_account_id, sale.in_token_paid_unclaimed);
435 | } else {
436 | let mut account = self.internal_unwrap_account(&sale.owner_id);
437 | if &sale.in_token_account_id != &self.treasury.skyward_token_id {
438 | let treasury_fee = sale.in_token_paid_unclaimed / TREASURY_FEE_DENOMINATOR;
439 | self.treasury
440 | .internal_deposit(&sale.in_token_account_id, treasury_fee);
441 | sale.in_token_paid_unclaimed -= treasury_fee;
442 | }
443 | account.internal_token_deposit(
444 | &sale.in_token_account_id,
445 | sale.in_token_paid_unclaimed,
446 | );
447 | self.accounts.insert(&sale.owner_id, &account.into());
448 | }
449 |
450 | sale.in_token_paid_unclaimed = 0;
451 | }
452 | let sale_ended = sale.has_ended();
453 | for out_token in &mut sale.out_tokens {
454 | if let Some(treasury_unclaimed) = &mut out_token.treasury_unclaimed {
455 | self.treasury
456 | .internal_deposit(&out_token.token_account_id, *treasury_unclaimed);
457 | *treasury_unclaimed = 0;
458 | }
459 | if sale_ended && out_token.remaining > 0 {
460 | // No one subscribed at the end of the sale
461 | if sale.owner_id == env::current_account_id() {
462 | self.treasury
463 | .internal_donate(&out_token.token_account_id, out_token.remaining);
464 | } else {
465 | let mut account = self.internal_unwrap_account(&sale.owner_id);
466 | account
467 | .internal_token_deposit(&out_token.token_account_id, out_token.remaining);
468 | self.accounts.insert(&sale.owner_id, &account.into());
469 | }
470 | out_token.distributed += out_token.remaining;
471 | out_token.remaining = 0;
472 | }
473 | }
474 | }
475 | }
476 |
477 | #[near_bindgen]
478 | impl Contract {
479 | #[payable]
480 | pub fn sale_create(&mut self, sale: SaleInput) -> u64 {
481 | let initial_storage_usage = env::storage_usage();
482 | let sale_id = self.num_sales;
483 | let sale = Sale::from_input(
484 | sale,
485 | env::predecessor_account_id(),
486 | &self.treasury.skyward_token_id,
487 | );
488 | sale.assert_valid_not_started();
489 |
490 | if &sale.owner_id == &env::current_account_id() {
491 | // Skyward Sale
492 | assert_eq!(
493 | sale.out_tokens.len(),
494 | 1,
495 | "{}",
496 | errors::INVALID_INITIAL_SKYWARD_SALE
497 | );
498 | assert_eq!(
499 | &sale.out_tokens[0].token_account_id,
500 | &self.treasury.skyward_token_id,
501 | "{}",
502 | errors::INVALID_INITIAL_SKYWARD_SALE
503 | );
504 | // Registering IN token into the treasury
505 | self.treasury.internal_deposit(&sale.in_token_account_id, 0);
506 | // Registering SKYWARD vesting schedule
507 | let mut skyward_vesting_schedule =
508 | self.treasury.skyward_vesting_schedule.get().unwrap();
509 | skyward_vesting_schedule.push(VestingInterval {
510 | start_timestamp: sale.start_time,
511 | end_timestamp: sale.start_time + sale.duration,
512 | amount: sale.out_tokens[0].remaining,
513 | });
514 | self.treasury
515 | .skyward_vesting_schedule
516 | .set(&skyward_vesting_schedule);
517 |
518 | self.sales.insert(&sale_id, &sale.into());
519 | self.num_sales += 1;
520 | } else {
521 | let mut account = self.internal_unwrap_account(&sale.owner_id);
522 | for out_token in &sale.out_tokens {
523 | if out_token.remaining > 0 {
524 | account
525 | .internal_token_withdraw(&out_token.token_account_id, out_token.remaining);
526 | }
527 | }
528 | self.internal_maybe_register_token(&mut account, &sale.in_token_account_id);
529 | account.sales.insert(&sale_id);
530 |
531 | self.accounts.insert(&sale.owner_id, &account.into());
532 | self.sales.insert(&sale_id, &sale.into());
533 | self.num_sales += 1;
534 |
535 | refund_extra_storage_deposit(
536 | env::storage_usage() - initial_storage_usage,
537 | self.treasury.listing_fee_near,
538 | );
539 | }
540 | sale_id
541 | }
542 |
543 | pub fn get_sale(&self, sale_id: u64, account_id: Option) -> Option {
544 | let account: Option = account_id
545 | .and_then(|account_id| self.accounts.get(account_id.as_ref()).map(|a| a.into()));
546 | self.internal_get_sale(sale_id, account.as_ref())
547 | }
548 |
549 | pub fn get_sales(
550 | &self,
551 | account_id: Option,
552 | from_index: Option,
553 | limit: Option,
554 | ) -> Vec {
555 | let account: Option = account_id
556 | .and_then(|account_id| self.accounts.get(account_id.as_ref()).map(|a| a.into()));
557 | let from_index = from_index.unwrap_or(0);
558 | let limit = limit.unwrap_or(self.num_sales);
559 | (from_index..std::cmp::min(from_index + limit, self.num_sales))
560 | .filter_map(|sale_id| self.internal_get_sale(sale_id, account.as_ref()))
561 | .collect()
562 | }
563 |
564 | pub fn get_sales_by_id(
565 | &self,
566 | account_id: Option,
567 | sale_ids: Vec,
568 | ) -> Vec {
569 | let account: Option = account_id
570 | .and_then(|account_id| self.accounts.get(account_id.as_ref()).map(|a| a.into()));
571 | sale_ids
572 | .into_iter()
573 | .filter_map(|sale_id| self.internal_get_sale(sale_id, account.as_ref()))
574 | .collect()
575 | }
576 |
577 | #[payable]
578 | pub fn sale_deposit_in_token(
579 | &mut self,
580 | sale_id: u64,
581 | amount: WrappedBalance,
582 | referral_id: Option,
583 | ) {
584 | assert_at_least_one_yocto();
585 | let initial_storage_usage = env::storage_usage();
586 | let account_id = env::predecessor_account_id();
587 |
588 | let referral_id = referral_id.map(|r| r.into());
589 | let in_amount = amount.0;
590 |
591 | let permissions_contract_id = self.internal_deposit_in_amount(
592 | sale_id,
593 | &account_id,
594 | in_amount,
595 | referral_id.as_ref(),
596 | false,
597 | );
598 |
599 | if let Some(permissions_contract_id) = permissions_contract_id {
600 | let attached_deposit = env::attached_deposit();
601 | self.treasury.locked_attached_deposits += env::attached_deposit();
602 | ext_permission_contract::is_approved(
603 | account_id.clone(),
604 | sale_id,
605 | &permissions_contract_id,
606 | NO_DEPOSIT,
607 | PERMISSION_CONTRACT_GAS,
608 | )
609 | .then(ext_self::after_is_approved(
610 | sale_id,
611 | account_id.clone(),
612 | in_amount.into(),
613 | referral_id,
614 | attached_deposit.into(),
615 | &env::current_account_id(),
616 | NO_DEPOSIT,
617 | AFTER_IS_APPROVED_GAS,
618 | ))
619 | .then(ext_self::maybe_refund_deposit(
620 | account_id.clone(),
621 | attached_deposit.into(),
622 | &env::current_account_id(),
623 | NO_DEPOSIT,
624 | MAYBE_REFUND_DEPOSIT_GAS,
625 | ))
626 | .as_return();
627 | } else {
628 | refund_extra_storage_deposit(env::storage_usage() - initial_storage_usage, 0);
629 | }
630 | }
631 |
632 | #[payable]
633 | pub fn sale_withdraw_in_token(&mut self, sale_id: u64, shares: Option) {
634 | assert_one_yocto();
635 | let initial_storage_usage = env::storage_usage();
636 | let account_id = env::predecessor_account_id();
637 | self.internal_withdraw_shares(sale_id, &account_id, shares.map(|s| s.0));
638 | refund_released_storage(&account_id, initial_storage_usage - env::storage_usage());
639 | }
640 |
641 | #[payable]
642 | pub fn sale_withdraw_in_token_exact(&mut self, sale_id: u64, amount: WrappedBalance) {
643 | assert_one_yocto();
644 | let initial_storage_usage = env::storage_usage();
645 | let account_id = env::predecessor_account_id();
646 | self.internal_withdraw_in_token_exact(sale_id, &account_id, amount.0);
647 | refund_released_storage(&account_id, initial_storage_usage - env::storage_usage());
648 | }
649 |
650 | /// This method can be called by anyone in order to move in tokens to treasury
651 | pub fn sale_distribute_unclaimed_tokens(&mut self, sale_id: u64) {
652 | let mut sale = self.internal_unwrap_sale(sale_id);
653 | self.internal_distribute_unclaimed_tokens(&mut sale);
654 | self.sales.insert(&sale_id, &sale.into());
655 | }
656 |
657 | pub fn sale_claim_out_tokens(&mut self, sale_id: u64) {
658 | let account_id = env::predecessor_account_id();
659 | let initial_storage_usage = env::storage_usage();
660 | let mut sale = self.internal_unwrap_sale(sale_id);
661 | self.internal_distribute_unclaimed_tokens(&mut sale);
662 | let mut account = self.internal_unwrap_account(&account_id);
663 | let subscription =
664 | self.internal_update_subscription(&mut account, sale_id, &mut sale, None, false);
665 |
666 | account.internal_save_subscription(sale_id, &sale, subscription);
667 |
668 | self.accounts.insert(&account_id, &account.into());
669 | self.sales.insert(&sale_id, &sale.into());
670 | refund_released_storage(&account_id, initial_storage_usage - env::storage_usage());
671 | }
672 | }
673 |
--------------------------------------------------------------------------------
/skyward/src/sub.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::json_types::WrappedBalance;
3 |
4 | #[derive(BorshSerialize, BorshDeserialize)]
5 | pub struct Subscription {
6 | pub shares: Balance,
7 | pub last_in_balance: Balance,
8 | pub spent_in_balance_without_shares: Balance,
9 | pub last_out_token_per_share: Vec,
10 | pub claimed_out_balance: Vec,
11 | pub referral_id: Option,
12 | }
13 |
14 | #[derive(BorshDeserialize, BorshSerialize)]
15 | pub enum VSubscription {
16 | Current(Subscription),
17 | }
18 |
19 | impl From for VSubscription {
20 | fn from(subscription: Subscription) -> Self {
21 | Self::Current(subscription)
22 | }
23 | }
24 |
25 | impl From for Subscription {
26 | fn from(v_subscription: VSubscription) -> Self {
27 | match v_subscription {
28 | VSubscription::Current(subscription) => subscription,
29 | }
30 | }
31 | }
32 |
33 | #[derive(Serialize, Deserialize)]
34 | #[serde(crate = "near_sdk::serde")]
35 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Clone))]
36 | pub struct SubscriptionOutput {
37 | pub remaining_in_balance: WrappedBalance,
38 | pub spent_in_balance: WrappedBalance,
39 | pub unclaimed_out_balances: Vec,
40 | pub claimed_out_balance: Vec,
41 | pub shares: WrappedBalance,
42 | pub referral_id: Option,
43 | }
44 |
45 | impl Subscription {
46 | pub fn touch(&mut self, sale: &Sale) -> Vec {
47 | let shares = U256::from(self.shares);
48 | let multiplier = U256::from(MULTIPLIER);
49 | self.last_out_token_per_share
50 | .iter_mut()
51 | .zip(sale.out_tokens.iter())
52 | .map(|(last_out_token_per_share, out_token)| {
53 | let out_token_per_share = U256(out_token.per_share.clone());
54 | let u256_last_out_token_per_share = U256(last_out_token_per_share.clone());
55 | let out_token_amount = if out_token_per_share == U256::zero() {
56 | 0
57 | } else {
58 | let diff = out_token_per_share - u256_last_out_token_per_share;
59 | (diff * shares / multiplier).as_u128()
60 | };
61 | *last_out_token_per_share = out_token_per_share.0;
62 | out_token_amount
63 | })
64 | .collect()
65 | }
66 |
67 | pub fn new(sale: &Sale, referral_id: Option) -> Self {
68 | Self {
69 | shares: 0,
70 | spent_in_balance_without_shares: 0,
71 | last_in_balance: 0,
72 | last_out_token_per_share: sale
73 | .out_tokens
74 | .iter()
75 | .map(|out_token| out_token.per_share.clone())
76 | .collect(),
77 | claimed_out_balance: vec![0; sale.out_tokens.len()],
78 | referral_id,
79 | }
80 | }
81 | }
82 |
83 | impl Contract {
84 | pub fn internal_withdraw_shares(
85 | &mut self,
86 | sale_id: u64,
87 | account_id: &AccountId,
88 | shares: Option,
89 | ) {
90 | let mut sale = self.internal_unwrap_sale(sale_id);
91 | self.internal_distribute_unclaimed_tokens(&mut sale);
92 | let mut account = self.internal_unwrap_account(account_id);
93 | let mut subscription =
94 | self.internal_update_subscription(&mut account, sale_id, &mut sale, None, false);
95 | let shares = shares.unwrap_or(subscription.shares);
96 | assert!(shares > 0, "{}", errors::ZERO_SHARES);
97 | assert!(
98 | shares <= subscription.shares,
99 | "{}",
100 | errors::NOT_ENOUGH_SHARES
101 | );
102 | let remaining_in_balance = sale.shares_to_in_balance(subscription.shares);
103 | subscription.spent_in_balance_without_shares +=
104 | subscription.last_in_balance - remaining_in_balance;
105 | subscription.shares -= shares;
106 | let in_token_amount = sale.shares_to_in_balance(shares);
107 | if in_token_amount > 0 {
108 | account.internal_token_deposit(&sale.in_token_account_id, in_token_amount);
109 | }
110 | sale.total_shares -= shares;
111 | sale.in_token_remaining -= in_token_amount;
112 |
113 | subscription.last_in_balance = sale.shares_to_in_balance(subscription.shares);
114 |
115 | account.internal_save_subscription(sale_id, &sale, subscription);
116 | self.accounts.insert(&account_id, &account.into());
117 | self.sales.insert(&sale_id, &sale.into());
118 | }
119 |
120 | pub fn internal_withdraw_in_token_exact(
121 | &mut self,
122 | sale_id: u64,
123 | account_id: &AccountId,
124 | in_amount: Balance,
125 | ) {
126 | let mut sale = self.internal_unwrap_sale(sale_id);
127 | self.internal_distribute_unclaimed_tokens(&mut sale);
128 | let mut account = self.internal_unwrap_account(account_id);
129 | let mut subscription =
130 | self.internal_update_subscription(&mut account, sale_id, &mut sale, None, false);
131 | assert!(in_amount > 0, "{}", errors::ZERO_IN_AMOUNT);
132 | let remaining_in_balance = sale.shares_to_in_balance(subscription.shares);
133 | assert!(
134 | in_amount <= remaining_in_balance,
135 | "{}",
136 | errors::NOT_ENOUGH_BALANCE
137 | );
138 | let shares = sale.in_amount_to_shares(in_amount, true);
139 | subscription.spent_in_balance_without_shares +=
140 | subscription.last_in_balance - remaining_in_balance;
141 | subscription.shares -= shares;
142 | account.internal_token_deposit(&sale.in_token_account_id, in_amount);
143 | sale.total_shares -= shares;
144 | sale.in_token_remaining -= in_amount;
145 |
146 | subscription.last_in_balance = sale.shares_to_in_balance(subscription.shares);
147 |
148 | account.internal_save_subscription(sale_id, &sale, subscription);
149 | self.accounts.insert(&account_id, &account.into());
150 | self.sales.insert(&sale_id, &sale.into());
151 | }
152 |
153 | pub fn internal_deposit_in_amount(
154 | &mut self,
155 | sale_id: u64,
156 | account_id: &AccountId,
157 | in_amount: Balance,
158 | referral_id: Option<&AccountId>,
159 | passed_permission_check: bool,
160 | ) -> Option {
161 | assert_ne!(referral_id, Some(account_id), "{}", errors::SELF_REFERRAL);
162 | assert!(in_amount > 0, "{}", errors::ZERO_IN_AMOUNT);
163 | let mut sale = self.internal_unwrap_sale(sale_id);
164 | self.internal_distribute_unclaimed_tokens(&mut sale);
165 | let mut account = self.internal_unwrap_account(account_id);
166 | if !passed_permission_check {
167 | if let Some(permissions_contract_id) = &sale.permissions_contract_id {
168 | if account.subs.get(&sale_id).is_none() {
169 | // Need to check permissions first
170 | return Some(permissions_contract_id.clone());
171 | }
172 | }
173 | }
174 |
175 | let mut subscription = self.internal_update_subscription(
176 | &mut account,
177 | sale_id,
178 | &mut sale,
179 | referral_id,
180 | passed_permission_check,
181 | );
182 |
183 | account.internal_token_withdraw(&sale.in_token_account_id, in_amount);
184 | for out_token in &sale.out_tokens {
185 | self.internal_maybe_register_token(&mut account, &out_token.token_account_id);
186 | }
187 | let remaining_in_balance = sale.shares_to_in_balance(subscription.shares);
188 | subscription.spent_in_balance_without_shares +=
189 | subscription.last_in_balance - remaining_in_balance;
190 | let shares = sale.in_amount_to_shares(in_amount, false);
191 | subscription.shares += shares;
192 | sale.total_shares += shares;
193 | sale.in_token_remaining += in_amount;
194 |
195 | subscription.last_in_balance = sale.shares_to_in_balance(subscription.shares);
196 |
197 | account.internal_save_subscription(sale_id, &sale, subscription);
198 | self.accounts.insert(&account_id, &account.into());
199 | self.sales.insert(&sale_id, &sale.into());
200 | None
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/skyward/src/treasury.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::collections::{LazyOption, UnorderedMap};
3 | use near_sdk::json_types::WrappedBalance;
4 | use near_sdk::{assert_one_yocto, Timestamp};
5 |
6 | #[derive(Serialize, Deserialize)]
7 | #[serde(crate = "near_sdk::serde")]
8 | #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
9 | pub struct VestingIntervalInput {
10 | pub start_timestamp: TimestampSec,
11 | pub end_timestamp: TimestampSec,
12 | pub amount: WrappedBalance,
13 | }
14 |
15 | #[derive(BorshDeserialize, BorshSerialize)]
16 | pub struct VestingInterval {
17 | pub start_timestamp: Timestamp,
18 | pub end_timestamp: Timestamp,
19 | pub amount: Balance,
20 | }
21 |
22 | impl From for VestingInterval {
23 | fn from(vs: VestingIntervalInput) -> Self {
24 | Self {
25 | start_timestamp: to_nano(vs.start_timestamp),
26 | end_timestamp: to_nano(vs.end_timestamp),
27 | amount: vs.amount.into(),
28 | }
29 | }
30 | }
31 |
32 | #[derive(BorshDeserialize, BorshSerialize)]
33 | pub struct Treasury {
34 | pub balances: UnorderedMap,
35 | pub skyward_token_id: TokenAccountId,
36 |
37 | pub skyward_burned_amount: Balance,
38 | pub skyward_vesting_schedule: LazyOption>,
39 |
40 | pub listing_fee_near: Balance,
41 |
42 | pub w_near_token_id: TokenAccountId,
43 |
44 | // The amount of NEAR locked while the permissions are being verified.
45 | pub locked_attached_deposits: Balance,
46 | }
47 |
48 | impl Treasury {
49 | pub fn new(
50 | skyward_token_id: TokenAccountId,
51 | skyward_vesting_schedule: Vec,
52 | listing_fee_near: Balance,
53 | w_near_token_id: TokenAccountId,
54 | ) -> Self {
55 | assert_ne!(skyward_token_id, w_near_token_id);
56 | Self {
57 | balances: UnorderedMap::new(StorageKey::TreasuryBalances),
58 | skyward_token_id,
59 | skyward_burned_amount: 0,
60 | skyward_vesting_schedule: LazyOption::new(
61 | StorageKey::VestingSchedule,
62 | Some(
63 | &skyward_vesting_schedule
64 | .into_iter()
65 | .map(|vs| vs.into())
66 | .collect(),
67 | ),
68 | ),
69 | listing_fee_near,
70 | w_near_token_id,
71 | locked_attached_deposits: 0,
72 | }
73 | }
74 |
75 | pub fn internal_deposit(&mut self, token_account_id: &AccountId, amount: Balance) {
76 | if token_account_id == &self.skyward_token_id {
77 | env::panic(errors::TREASURY_CAN_NOT_CONTAIN_SKYWARD.as_bytes());
78 | }
79 | let balance = self.balances.get(token_account_id).unwrap_or(0);
80 | let new_balance = balance.checked_add(amount).expect(errors::BALANCE_OVERFLOW);
81 | self.balances.insert(&token_account_id, &new_balance);
82 | }
83 |
84 | pub fn internal_withdraw(&mut self, token_account_id: &AccountId, amount: Balance) {
85 | let balance = self.balances.get(token_account_id).unwrap_or(0);
86 | let new_balance = balance
87 | .checked_sub(amount)
88 | .expect(errors::NOT_ENOUGH_BALANCE);
89 | self.balances.insert(&token_account_id, &new_balance);
90 | }
91 |
92 | pub fn internal_donate(&mut self, token_account_id: &AccountId, amount: Balance) {
93 | if token_account_id == &self.skyward_token_id {
94 | self.skyward_burned_amount += amount;
95 | } else {
96 | self.internal_deposit(token_account_id, amount);
97 | }
98 | }
99 | }
100 |
101 | #[near_bindgen]
102 | impl Contract {
103 | pub fn get_treasury_balance(&self, token_account_id: ValidAccountId) -> Option {
104 | self.treasury
105 | .balances
106 | .get(token_account_id.as_ref())
107 | .map(|a| a.into())
108 | }
109 |
110 | pub fn get_treasury_balances(
111 | &self,
112 | from_index: Option,
113 | limit: Option,
114 | ) -> Vec<(TokenAccountId, WrappedBalance)> {
115 | let keys = self.treasury.balances.keys_as_vector();
116 | let values = self.treasury.balances.values_as_vector();
117 | let from_index = from_index.unwrap_or(0);
118 | let limit = limit.unwrap_or(keys.len());
119 | (from_index..std::cmp::min(from_index + limit, keys.len()))
120 | .map(|index| (keys.get(index).unwrap(), values.get(index).unwrap().into()))
121 | .collect()
122 | }
123 |
124 | pub fn get_treasury_num_balances(&self) -> u64 {
125 | self.treasury.balances.len()
126 | }
127 |
128 | pub fn get_skyward_token_id(self) -> TokenAccountId {
129 | self.treasury.skyward_token_id
130 | }
131 |
132 | pub fn get_skyward_circulating_supply(&self) -> WrappedBalance {
133 | let mut balance = 0;
134 | let skyward_vesting_schedule = self.treasury.skyward_vesting_schedule.get().unwrap();
135 | let current_timestamp = env::block_timestamp();
136 | for vesting_interval in skyward_vesting_schedule {
137 | balance += if current_timestamp <= vesting_interval.start_timestamp {
138 | 0
139 | } else if current_timestamp >= vesting_interval.end_timestamp {
140 | vesting_interval.amount
141 | } else {
142 | let total_duration =
143 | vesting_interval.end_timestamp - vesting_interval.start_timestamp;
144 | let passed_duration = current_timestamp - vesting_interval.start_timestamp;
145 | (U256::from(passed_duration) * U256::from(vesting_interval.amount)
146 | / U256::from(total_duration))
147 | .as_u128()
148 | };
149 | }
150 | (balance - self.treasury.skyward_burned_amount).into()
151 | }
152 |
153 | pub fn get_listing_fee(&self) -> WrappedBalance {
154 | self.treasury.listing_fee_near.into()
155 | }
156 |
157 | #[payable]
158 | pub fn redeem_skyward(
159 | &mut self,
160 | skyward_amount: WrappedBalance,
161 | token_account_ids: Vec,
162 | ) {
163 | assert_one_yocto();
164 | let skyward_amount: Balance = skyward_amount.into();
165 | assert!(skyward_amount > 0, "{}", errors::ZERO_SKYWARD);
166 | let account_id = env::predecessor_account_id();
167 | let mut account = self.internal_unwrap_account(&account_id);
168 | account.internal_token_withdraw(&self.treasury.skyward_token_id, skyward_amount);
169 | let numerator = U256::from(skyward_amount);
170 | let denominator = U256::from(self.get_skyward_circulating_supply().0);
171 | self.treasury.skyward_burned_amount += skyward_amount;
172 | for token_account_id in token_account_ids {
173 | let treasury_balance = self
174 | .treasury
175 | .balances
176 | .get(token_account_id.as_ref())
177 | .expect(errors::TOKEN_NOT_REGISTERED);
178 | let amount = (U256::from(treasury_balance) * numerator / denominator).as_u128();
179 | if amount > 0 {
180 | let new_balance = treasury_balance
181 | .checked_sub(amount)
182 | .expect(errors::NOT_ENOUGH_BALANCE);
183 | self.treasury
184 | .balances
185 | .insert(token_account_id.as_ref(), &new_balance);
186 | account.internal_token_deposit(token_account_id.as_ref(), amount);
187 | }
188 | }
189 | self.accounts.insert(&account_id, &account.into());
190 | }
191 |
192 | pub fn wrap_extra_near(&mut self) -> Promise {
193 | let unused_near_balance = env::account_balance()
194 | - Balance::from(env::storage_usage()) * env::storage_byte_cost()
195 | - self.treasury.locked_attached_deposits;
196 | assert!(
197 | unused_near_balance > MIN_EXTRA_NEAR,
198 | "{}",
199 | errors::NOT_ENOUGH_BALANCE
200 | );
201 | let extra_near = unused_near_balance - EXTRA_NEAR;
202 | Promise::new(self.treasury.w_near_token_id.clone())
203 | .function_call(
204 | b"storage_deposit".to_vec(),
205 | b"{}".to_vec(),
206 | STORAGE_DEPOSIT,
207 | STORAGE_DEPOSIT_GAS,
208 | )
209 | .function_call(
210 | b"near_deposit".to_vec(),
211 | b"{}".to_vec(),
212 | extra_near,
213 | NEAR_DEPOSIT_GAS,
214 | )
215 | .then(ext_self::after_near_deposit(
216 | extra_near.into(),
217 | &env::current_account_id(),
218 | NO_DEPOSIT,
219 | AFTER_NEAR_DEPOSIT_GAS,
220 | ))
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/skyward/src/utils.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::{Gas, Timestamp};
3 |
4 | pub(crate) const NO_DEPOSIT: Balance = 0;
5 | pub(crate) const ONE_YOCTO: Balance = 1;
6 | pub(crate) const ONE_NEAR: Balance = 10u128.pow(24);
7 |
8 | pub(crate) const STORAGE_DEPOSIT: Balance = 125 * env::STORAGE_PRICE_PER_BYTE;
9 | pub(crate) const EXTRA_NEAR_FOR_STORAGE: Balance = 1000 * env::STORAGE_PRICE_PER_BYTE;
10 | pub(crate) const EXTRA_NEAR: Balance = EXTRA_NEAR_FOR_STORAGE + STORAGE_DEPOSIT;
11 | pub(crate) const MIN_EXTRA_NEAR: Balance = EXTRA_NEAR + ONE_NEAR;
12 |
13 | const BASE_GAS: Gas = 5_000_000_000_000;
14 | pub(crate) const FT_TRANSFER_GAS: Gas = BASE_GAS;
15 | pub(crate) const AFTER_FT_TRANSFER_GAS: Gas = BASE_GAS;
16 | pub(crate) const AFTER_NEAR_DEPOSIT_GAS: Gas = BASE_GAS;
17 |
18 | pub(crate) const STORAGE_DEPOSIT_GAS: Gas = BASE_GAS * 2;
19 | pub(crate) const NEAR_DEPOSIT_GAS: Gas = BASE_GAS;
20 |
21 | pub(crate) const PERMISSION_CONTRACT_GAS: Gas = BASE_GAS * 10;
22 | pub(crate) const AFTER_IS_APPROVED_GAS: Gas = BASE_GAS * 4;
23 | pub(crate) const MAYBE_REFUND_DEPOSIT_GAS: Gas = BASE_GAS * 2;
24 |
25 | pub type TimestampSec = u32;
26 | pub type BasicPoints = u16;
27 |
28 | uint::construct_uint! {
29 | pub struct U256(4);
30 | }
31 |
32 | pub(crate) type InnerU256 = [u64; 4];
33 | pub(crate) type TokenAccountId = AccountId;
34 |
35 | uint::construct_uint! {
36 | pub struct U384(6);
37 | }
38 |
39 | pub(crate) fn refund_extra_storage_deposit(storage_used: StorageUsage, used_balance: Balance) {
40 | let required_cost = env::storage_byte_cost() * Balance::from(storage_used);
41 | let attached_deposit = env::attached_deposit()
42 | .checked_sub(used_balance)
43 | .expect(errors::NOT_ENOUGH_ATTACHED_BALANCE);
44 |
45 | assert!(
46 | required_cost <= attached_deposit,
47 | "{} {}",
48 | errors::NOT_ENOUGH_ATTACHED_BALANCE,
49 | required_cost,
50 | );
51 |
52 | let refund = attached_deposit - required_cost;
53 | if refund > 1 {
54 | Promise::new(env::predecessor_account_id()).transfer(refund);
55 | }
56 | }
57 |
58 | pub(crate) fn refund_released_storage(account_id: &AccountId, storage_released: StorageUsage) {
59 | if storage_released > 0 {
60 | let refund =
61 | env::storage_byte_cost() * Balance::from(storage_released) + env::attached_deposit();
62 | Promise::new(account_id.clone()).transfer(refund);
63 | }
64 | }
65 |
66 | pub(crate) fn assert_at_least_one_yocto() {
67 | assert!(
68 | env::attached_deposit() >= ONE_YOCTO,
69 | "{}",
70 | errors::NEED_AT_LEAST_ONE_YOCTO
71 | )
72 | }
73 |
74 | pub(crate) fn to_nano(timestamp: TimestampSec) -> Timestamp {
75 | Timestamp::from(timestamp) * 10u64.pow(9)
76 | }
77 |
--------------------------------------------------------------------------------
/test_all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | pushd skyward
6 | cargo test
7 | popd
8 |
9 | pushd lockup
10 | cargo test
11 | popd
12 |
13 | popd
14 |
--------------------------------------------------------------------------------
/token_swap_testnet/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "Inflector"
5 | version = "0.11.4"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
8 |
9 | [[package]]
10 | name = "ahash"
11 | version = "0.4.7"
12 | source = "registry+https://github.com/rust-lang/crates.io-index"
13 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
14 |
15 | [[package]]
16 | name = "aho-corasick"
17 | version = "0.7.15"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
20 | dependencies = [
21 | "memchr",
22 | ]
23 |
24 | [[package]]
25 | name = "autocfg"
26 | version = "1.0.1"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
29 |
30 | [[package]]
31 | name = "base64"
32 | version = "0.13.0"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
35 |
36 | [[package]]
37 | name = "block-buffer"
38 | version = "0.9.0"
39 | source = "registry+https://github.com/rust-lang/crates.io-index"
40 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
41 | dependencies = [
42 | "block-padding",
43 | "generic-array",
44 | ]
45 |
46 | [[package]]
47 | name = "block-padding"
48 | version = "0.2.1"
49 | source = "registry+https://github.com/rust-lang/crates.io-index"
50 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
51 |
52 | [[package]]
53 | name = "borsh"
54 | version = "0.8.2"
55 | source = "registry+https://github.com/rust-lang/crates.io-index"
56 | checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
57 | dependencies = [
58 | "borsh-derive",
59 | "hashbrown",
60 | ]
61 |
62 | [[package]]
63 | name = "borsh-derive"
64 | version = "0.8.2"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
67 | dependencies = [
68 | "borsh-derive-internal",
69 | "borsh-schema-derive-internal",
70 | "proc-macro-crate",
71 | "proc-macro2",
72 | "syn",
73 | ]
74 |
75 | [[package]]
76 | name = "borsh-derive-internal"
77 | version = "0.8.2"
78 | source = "registry+https://github.com/rust-lang/crates.io-index"
79 | checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
80 | dependencies = [
81 | "proc-macro2",
82 | "quote",
83 | "syn",
84 | ]
85 |
86 | [[package]]
87 | name = "borsh-schema-derive-internal"
88 | version = "0.8.2"
89 | source = "registry+https://github.com/rust-lang/crates.io-index"
90 | checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
91 | dependencies = [
92 | "proc-macro2",
93 | "quote",
94 | "syn",
95 | ]
96 |
97 | [[package]]
98 | name = "bs58"
99 | version = "0.4.0"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
102 |
103 | [[package]]
104 | name = "byteorder"
105 | version = "1.4.3"
106 | source = "registry+https://github.com/rust-lang/crates.io-index"
107 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
108 |
109 | [[package]]
110 | name = "cfg-if"
111 | version = "0.1.10"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
114 |
115 | [[package]]
116 | name = "cfg-if"
117 | version = "1.0.0"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
120 |
121 | [[package]]
122 | name = "convert_case"
123 | version = "0.4.0"
124 | source = "registry+https://github.com/rust-lang/crates.io-index"
125 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
126 |
127 | [[package]]
128 | name = "cpuid-bool"
129 | version = "0.1.2"
130 | source = "registry+https://github.com/rust-lang/crates.io-index"
131 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
132 |
133 | [[package]]
134 | name = "derive_more"
135 | version = "0.99.13"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6"
138 | dependencies = [
139 | "convert_case",
140 | "proc-macro2",
141 | "quote",
142 | "syn",
143 | ]
144 |
145 | [[package]]
146 | name = "digest"
147 | version = "0.9.0"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
150 | dependencies = [
151 | "generic-array",
152 | ]
153 |
154 | [[package]]
155 | name = "generic-array"
156 | version = "0.14.4"
157 | source = "registry+https://github.com/rust-lang/crates.io-index"
158 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
159 | dependencies = [
160 | "typenum",
161 | "version_check",
162 | ]
163 |
164 | [[package]]
165 | name = "hashbrown"
166 | version = "0.9.1"
167 | source = "registry+https://github.com/rust-lang/crates.io-index"
168 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
169 | dependencies = [
170 | "ahash",
171 | ]
172 |
173 | [[package]]
174 | name = "hex"
175 | version = "0.4.3"
176 | source = "registry+https://github.com/rust-lang/crates.io-index"
177 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
178 |
179 | [[package]]
180 | name = "indexmap"
181 | version = "1.6.2"
182 | source = "registry+https://github.com/rust-lang/crates.io-index"
183 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
184 | dependencies = [
185 | "autocfg",
186 | "hashbrown",
187 | ]
188 |
189 | [[package]]
190 | name = "itoa"
191 | version = "0.4.7"
192 | source = "registry+https://github.com/rust-lang/crates.io-index"
193 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
194 |
195 | [[package]]
196 | name = "keccak"
197 | version = "0.1.0"
198 | source = "registry+https://github.com/rust-lang/crates.io-index"
199 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
200 |
201 | [[package]]
202 | name = "lazy_static"
203 | version = "1.4.0"
204 | source = "registry+https://github.com/rust-lang/crates.io-index"
205 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
206 |
207 | [[package]]
208 | name = "libc"
209 | version = "0.2.94"
210 | source = "registry+https://github.com/rust-lang/crates.io-index"
211 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
212 |
213 | [[package]]
214 | name = "memchr"
215 | version = "2.3.4"
216 | source = "registry+https://github.com/rust-lang/crates.io-index"
217 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
218 |
219 | [[package]]
220 | name = "memory_units"
221 | version = "0.4.0"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
224 |
225 | [[package]]
226 | name = "near-contract-standards"
227 | version = "3.1.0"
228 | source = "registry+https://github.com/rust-lang/crates.io-index"
229 | checksum = "d5837ffd278eeedc4f97104586b3851ed9e7c32449de8b54e8d752f22498588e"
230 | dependencies = [
231 | "near-sdk",
232 | ]
233 |
234 | [[package]]
235 | name = "near-primitives-core"
236 | version = "0.4.0"
237 | source = "registry+https://github.com/rust-lang/crates.io-index"
238 | checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52"
239 | dependencies = [
240 | "base64",
241 | "borsh",
242 | "bs58",
243 | "derive_more",
244 | "hex",
245 | "lazy_static",
246 | "num-rational",
247 | "serde",
248 | "serde_json",
249 | "sha2",
250 | ]
251 |
252 | [[package]]
253 | name = "near-rpc-error-core"
254 | version = "0.1.0"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9"
257 | dependencies = [
258 | "proc-macro2",
259 | "quote",
260 | "serde",
261 | "serde_json",
262 | "syn",
263 | ]
264 |
265 | [[package]]
266 | name = "near-rpc-error-macro"
267 | version = "0.1.0"
268 | source = "registry+https://github.com/rust-lang/crates.io-index"
269 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9"
270 | dependencies = [
271 | "near-rpc-error-core",
272 | "proc-macro2",
273 | "quote",
274 | "serde",
275 | "serde_json",
276 | "syn",
277 | ]
278 |
279 | [[package]]
280 | name = "near-runtime-utils"
281 | version = "4.0.0-pre.1"
282 | source = "registry+https://github.com/rust-lang/crates.io-index"
283 | checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07"
284 | dependencies = [
285 | "lazy_static",
286 | "regex",
287 | ]
288 |
289 | [[package]]
290 | name = "near-sdk"
291 | version = "3.1.0"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "c7383e242d3e07bf0951e8589d6eebd7f18bb1c1fc5fbec3fad796041a6aebd1"
294 | dependencies = [
295 | "base64",
296 | "borsh",
297 | "bs58",
298 | "near-primitives-core",
299 | "near-sdk-macros",
300 | "near-vm-logic",
301 | "serde",
302 | "serde_json",
303 | "wee_alloc",
304 | ]
305 |
306 | [[package]]
307 | name = "near-sdk-core"
308 | version = "3.1.0"
309 | source = "registry+https://github.com/rust-lang/crates.io-index"
310 | checksum = "284a78d9eb8eda58330462fa0023a6d7014c941df1f0387095e7dfd1dc0f2bce"
311 | dependencies = [
312 | "Inflector",
313 | "proc-macro2",
314 | "quote",
315 | "syn",
316 | ]
317 |
318 | [[package]]
319 | name = "near-sdk-macros"
320 | version = "3.1.0"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | checksum = "2037337438f97d1ce5f7c896cf229dc56dacd5c01142d1ef95a7d778cde6ce7d"
323 | dependencies = [
324 | "near-sdk-core",
325 | "proc-macro2",
326 | "quote",
327 | "syn",
328 | ]
329 |
330 | [[package]]
331 | name = "near-vm-errors"
332 | version = "4.0.0-pre.1"
333 | source = "registry+https://github.com/rust-lang/crates.io-index"
334 | checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7"
335 | dependencies = [
336 | "borsh",
337 | "hex",
338 | "near-rpc-error-macro",
339 | "serde",
340 | ]
341 |
342 | [[package]]
343 | name = "near-vm-logic"
344 | version = "4.0.0-pre.1"
345 | source = "registry+https://github.com/rust-lang/crates.io-index"
346 | checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5"
347 | dependencies = [
348 | "base64",
349 | "borsh",
350 | "bs58",
351 | "byteorder",
352 | "near-primitives-core",
353 | "near-runtime-utils",
354 | "near-vm-errors",
355 | "serde",
356 | "sha2",
357 | "sha3",
358 | ]
359 |
360 | [[package]]
361 | name = "num-bigint"
362 | version = "0.3.2"
363 | source = "registry+https://github.com/rust-lang/crates.io-index"
364 | checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba"
365 | dependencies = [
366 | "autocfg",
367 | "num-integer",
368 | "num-traits",
369 | ]
370 |
371 | [[package]]
372 | name = "num-integer"
373 | version = "0.1.44"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
376 | dependencies = [
377 | "autocfg",
378 | "num-traits",
379 | ]
380 |
381 | [[package]]
382 | name = "num-rational"
383 | version = "0.3.2"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
386 | dependencies = [
387 | "autocfg",
388 | "num-bigint",
389 | "num-integer",
390 | "num-traits",
391 | "serde",
392 | ]
393 |
394 | [[package]]
395 | name = "num-traits"
396 | version = "0.2.14"
397 | source = "registry+https://github.com/rust-lang/crates.io-index"
398 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
399 | dependencies = [
400 | "autocfg",
401 | ]
402 |
403 | [[package]]
404 | name = "opaque-debug"
405 | version = "0.3.0"
406 | source = "registry+https://github.com/rust-lang/crates.io-index"
407 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
408 |
409 | [[package]]
410 | name = "proc-macro-crate"
411 | version = "0.1.5"
412 | source = "registry+https://github.com/rust-lang/crates.io-index"
413 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
414 | dependencies = [
415 | "toml",
416 | ]
417 |
418 | [[package]]
419 | name = "proc-macro2"
420 | version = "1.0.26"
421 | source = "registry+https://github.com/rust-lang/crates.io-index"
422 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
423 | dependencies = [
424 | "unicode-xid",
425 | ]
426 |
427 | [[package]]
428 | name = "quote"
429 | version = "1.0.9"
430 | source = "registry+https://github.com/rust-lang/crates.io-index"
431 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
432 | dependencies = [
433 | "proc-macro2",
434 | ]
435 |
436 | [[package]]
437 | name = "regex"
438 | version = "1.4.5"
439 | source = "registry+https://github.com/rust-lang/crates.io-index"
440 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
441 | dependencies = [
442 | "aho-corasick",
443 | "memchr",
444 | "regex-syntax",
445 | ]
446 |
447 | [[package]]
448 | name = "regex-syntax"
449 | version = "0.6.23"
450 | source = "registry+https://github.com/rust-lang/crates.io-index"
451 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
452 |
453 | [[package]]
454 | name = "ryu"
455 | version = "1.0.5"
456 | source = "registry+https://github.com/rust-lang/crates.io-index"
457 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
458 |
459 | [[package]]
460 | name = "serde"
461 | version = "1.0.118"
462 | source = "registry+https://github.com/rust-lang/crates.io-index"
463 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
464 | dependencies = [
465 | "serde_derive",
466 | ]
467 |
468 | [[package]]
469 | name = "serde_derive"
470 | version = "1.0.118"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
473 | dependencies = [
474 | "proc-macro2",
475 | "quote",
476 | "syn",
477 | ]
478 |
479 | [[package]]
480 | name = "serde_json"
481 | version = "1.0.64"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
484 | dependencies = [
485 | "indexmap",
486 | "itoa",
487 | "ryu",
488 | "serde",
489 | ]
490 |
491 | [[package]]
492 | name = "sha2"
493 | version = "0.9.3"
494 | source = "registry+https://github.com/rust-lang/crates.io-index"
495 | checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
496 | dependencies = [
497 | "block-buffer",
498 | "cfg-if 1.0.0",
499 | "cpuid-bool",
500 | "digest",
501 | "opaque-debug",
502 | ]
503 |
504 | [[package]]
505 | name = "sha3"
506 | version = "0.9.1"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
509 | dependencies = [
510 | "block-buffer",
511 | "digest",
512 | "keccak",
513 | "opaque-debug",
514 | ]
515 |
516 | [[package]]
517 | name = "syn"
518 | version = "1.0.57"
519 | source = "registry+https://github.com/rust-lang/crates.io-index"
520 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
521 | dependencies = [
522 | "proc-macro2",
523 | "quote",
524 | "unicode-xid",
525 | ]
526 |
527 | [[package]]
528 | name = "token-swap-testnet"
529 | version = "0.1.0"
530 | dependencies = [
531 | "near-contract-standards",
532 | "near-sdk",
533 | ]
534 |
535 | [[package]]
536 | name = "toml"
537 | version = "0.5.8"
538 | source = "registry+https://github.com/rust-lang/crates.io-index"
539 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
540 | dependencies = [
541 | "serde",
542 | ]
543 |
544 | [[package]]
545 | name = "typenum"
546 | version = "1.13.0"
547 | source = "registry+https://github.com/rust-lang/crates.io-index"
548 | checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
549 |
550 | [[package]]
551 | name = "unicode-xid"
552 | version = "0.2.1"
553 | source = "registry+https://github.com/rust-lang/crates.io-index"
554 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
555 |
556 | [[package]]
557 | name = "version_check"
558 | version = "0.9.3"
559 | source = "registry+https://github.com/rust-lang/crates.io-index"
560 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
561 |
562 | [[package]]
563 | name = "wee_alloc"
564 | version = "0.4.5"
565 | source = "registry+https://github.com/rust-lang/crates.io-index"
566 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
567 | dependencies = [
568 | "cfg-if 0.1.10",
569 | "libc",
570 | "memory_units",
571 | "winapi",
572 | ]
573 |
574 | [[package]]
575 | name = "winapi"
576 | version = "0.3.9"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
579 | dependencies = [
580 | "winapi-i686-pc-windows-gnu",
581 | "winapi-x86_64-pc-windows-gnu",
582 | ]
583 |
584 | [[package]]
585 | name = "winapi-i686-pc-windows-gnu"
586 | version = "0.4.0"
587 | source = "registry+https://github.com/rust-lang/crates.io-index"
588 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
589 |
590 | [[package]]
591 | name = "winapi-x86_64-pc-windows-gnu"
592 | version = "0.4.0"
593 | source = "registry+https://github.com/rust-lang/crates.io-index"
594 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
595 |
--------------------------------------------------------------------------------
/token_swap_testnet/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "token-swap-testnet"
3 | version = "0.1.0"
4 | authors = ["Spensa Nightshade "]
5 | edition = "2018"
6 |
7 | [lib]
8 | crate-type = ["cdylib", "rlib"]
9 |
10 | [dependencies]
11 | near-sdk = "3.1.0"
12 | near-contract-standards = "3.1.0"
13 |
14 | [profile.release]
15 | codegen-units=1
16 | opt-level = "z"
17 | lto = true
18 | debug = false
19 | panic = "abort"
20 | overflow-checks = true
21 |
--------------------------------------------------------------------------------
/token_swap_testnet/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | pushd "$(dirname $0)"
4 |
5 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
6 | mkdir -p ./res
7 | cp target/wasm32-unknown-unknown/release/token_swap_testnet.wasm ./res/
8 |
9 | popd
10 |
--------------------------------------------------------------------------------
/token_swap_testnet/src/lib.rs:
--------------------------------------------------------------------------------
1 | use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver;
2 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
3 | use near_sdk::collections::UnorderedMap;
4 | use near_sdk::json_types::{ValidAccountId, WrappedBalance, U128};
5 | use near_sdk::serde::{Deserialize, Serialize};
6 | use near_sdk::{
7 | env, near_bindgen, serde_json, AccountId, Balance, BorshStorageKey, PanicOnDefault,
8 | PromiseOrValue,
9 | };
10 |
11 | near_sdk::setup_alloc!();
12 |
13 | #[derive(BorshStorageKey, BorshSerialize)]
14 | pub(crate) enum StorageKey {
15 | Accounts,
16 | }
17 |
18 | #[near_bindgen]
19 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
20 | pub struct Contract {
21 | pub accounts: UnorderedMap,
22 |
23 | pub token_account_id: AccountId,
24 | }
25 |
26 | #[near_bindgen]
27 | impl Contract {
28 | #[init]
29 | pub fn new(token_account_id: ValidAccountId) -> Self {
30 | Self {
31 | accounts: UnorderedMap::new(StorageKey::Accounts),
32 | token_account_id: token_account_id.into(),
33 | }
34 | }
35 |
36 | pub fn get_balance(&self, account_id: ValidAccountId) -> WrappedBalance {
37 | self.accounts.get(account_id.as_ref()).unwrap_or(0).into()
38 | }
39 |
40 | pub fn get_accounts(
41 | &self,
42 | from_index: Option,
43 | limit: Option,
44 | ) -> Vec<(AccountId, WrappedBalance)> {
45 | let from_index = from_index.unwrap_or(0);
46 | let limit = limit.unwrap_or(u64::MAX);
47 | let keys = self.accounts.keys_as_vector();
48 | let values = self.accounts.values_as_vector();
49 | (from_index..std::cmp::min(from_index.saturating_add(limit), keys.len()))
50 | .map(|index| (keys.get(index).unwrap(), values.get(index).unwrap().into()))
51 | .collect()
52 | }
53 | }
54 |
55 | impl Contract {
56 | fn internal_token_deposit(&mut self, account_id: &AccountId, amount: Balance) {
57 | let current_balance = self.accounts.get(&account_id).unwrap_or(0);
58 | self.accounts
59 | .insert(&account_id, &(current_balance + amount));
60 | }
61 | }
62 |
63 | #[derive(Serialize, Deserialize)]
64 | #[serde(crate = "near_sdk::serde")]
65 | pub enum FtOnTransferArgs {
66 | LinkMainnetAccount { account_id: ValidAccountId },
67 | }
68 |
69 | const ERR_FAILED_TO_PARSE_FT_ON_TRANSFER_MSG: &str = "ERR_FAILED_TO_PARSE_FT_ON_TRANSFER_MSG";
70 | const ERR_INVALID_FT_ACCOUNT_ID: &str = "ERR_INVALID_FT_ACCOUNT_ID";
71 |
72 | #[near_bindgen]
73 | impl FungibleTokenReceiver for Contract {
74 | #[allow(unused_variables)]
75 | fn ft_on_transfer(
76 | &mut self,
77 | sender_id: ValidAccountId,
78 | amount: U128,
79 | msg: String,
80 | ) -> PromiseOrValue {
81 | let args: FtOnTransferArgs =
82 | serde_json::from_str(&msg).expect(ERR_FAILED_TO_PARSE_FT_ON_TRANSFER_MSG);
83 | let token_account_id = env::predecessor_account_id();
84 | assert_eq!(
85 | &self.token_account_id, &token_account_id,
86 | "{}",
87 | ERR_INVALID_FT_ACCOUNT_ID
88 | );
89 | match args {
90 | FtOnTransferArgs::LinkMainnetAccount { account_id } => {
91 | self.internal_token_deposit(account_id.as_ref(), amount.0);
92 | }
93 | }
94 | PromiseOrValue::Value(0.into())
95 | }
96 | }
97 |
--------------------------------------------------------------------------------