├── .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 | --------------------------------------------------------------------------------