diff --git a/ref-exchange/Cargo.toml b/ref-exchange/Cargo.toml index 2daf77f..1177290 100644 --- a/ref-exchange/Cargo.toml +++ b/ref-exchange/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ref-exchange" -version = "1.4.1" +version = "1.4.2" authors = ["Illia Polosukhin "] edition = "2018" publish = false @@ -9,7 +9,7 @@ publish = false crate-type = ["cdylib", "rlib"] [dependencies] -uint = { version = "0.9.0", default-features = false } +uint = { version = "0.9.3", default-features = false } near-sdk = "3.1.0" near-contract-standards = "3.1.0" diff --git a/ref-exchange/build_docker.sh b/ref-exchange/build_docker.sh index 10ea816..aa8e7ae 100755 --- a/ref-exchange/build_docker.sh +++ b/ref-exchange/build_docker.sh @@ -22,7 +22,7 @@ docker create \ fi docker start $NAME -docker exec -it $NAME /bin/bash -c "rustup toolchain install stable-2020-10-08; rustup default stable-2020-10-08; rustup target add wasm32-unknown-unknown; cargo build --target wasm32-unknown-unknown --release" +docker exec -it $NAME /bin/bash -c "rustup toolchain install stable-2021-11-01; rustup default stable-2021-11-01; rustup target add wasm32-unknown-unknown; cargo build --target wasm32-unknown-unknown --release" mkdir -p res cp $DIR/../target/wasm32-unknown-unknown/release/ref_exchange.wasm $DIR/../res/ref_exchange_release.wasm diff --git a/ref-exchange/src/internals.rs b/ref-exchange/src/internals.rs new file mode 100644 index 0000000..5bb8aa6 --- /dev/null +++ b/ref-exchange/src/internals.rs @@ -0,0 +1,252 @@ +use crate::errors::*; +use crate::*; +use near_sdk::{env, json_types::U128, AccountId, Balance}; + +/// an virtual inner account (won't appear in storage), used for instant swap +pub const VIRTUAL_ACC: &str = "@"; +/// a special inner account, used to store all inner MFT when they act as any pool's backend assets +pub const MFT_LOCKER: &str = "_MFT_LOCKER@"; + +/// Message parameters to receive via token function call. +/// used for instant swap. +#[derive(Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +#[serde(untagged)] +pub(crate) enum TokenReceiverMessage { + /// Alternative to deposit + execute actions call. + Execute { + referral_id: Option, + /// List of sequential actions. + actions: Vec, + }, +} + +/// All kinds of token accepted in the contract. +/// basically, for mft token, the format is {token_contract_id:inner_id} +/// for nep-141 token, the format is {token_contract_id} +pub enum TokenType { + Nep141 {token_id: AccountId}, + InnerMFT {pool_id: u64}, + OuterMFT { token_contract_id: AccountId, inner_id: u64}, + Illegal, +} + +impl TokenType { + pub fn parse_token(token: &String) -> Self { + let parts: Vec<&str> = token.split(":").collect(); + if parts.len() == 2 { + if let Ok(pool_id) = str::parse::(parts[1]) { + if parts[0].to_string() == env::current_account_id() || parts[0].to_string() == "" { + TokenType::InnerMFT {pool_id,} + } else { + // outer mft + TokenType::OuterMFT { + token_contract_id: parts[0].to_string(), + inner_id: pool_id, + } + } + } else { + TokenType::Illegal + } + } else { + TokenType::Nep141 {token_id: parts[0].to_string()} + } + } +} + +/// Internal methods implementation. +impl Contract { + + pub(crate) fn assert_contract_running(&self) { + match self.state { + RunningState::Running => (), + _ => env::panic(ERR51_CONTRACT_PAUSED.as_bytes()), + }; + } + + /// Check how much storage taken costs and refund the left over back. + pub(crate) fn internal_check_storage(&self, prev_storage: StorageUsage) { + let storage_cost = env::storage_usage() + .checked_sub(prev_storage) + .unwrap_or_default() as Balance + * env::storage_byte_cost(); + + let refund = env::attached_deposit() + .checked_sub(storage_cost) + .expect( + format!( + "ERR_STORAGE_DEPOSIT need {}, attatched {}", + storage_cost, env::attached_deposit() + ).as_str() + ); + if refund > 0 { + Promise::new(env::predecessor_account_id()).transfer(refund); + } + } + + /// Adds given pool to the list and returns it's id. + /// If there is not enough attached balance to cover storage, fails. + /// If too much attached - refunds it back. + pub(crate) fn internal_add_pool(&mut self, mut pool: Pool) -> u64 { + let prev_storage = env::storage_usage(); + let id = self.pools.len() as u64; + // exchange share was registered at creation time + pool.share_register(&env::current_account_id()); + self.pools.push(&pool); + self.internal_check_storage(prev_storage); + id + } + + /// Execute sequence of actions on given account. Modifies passed account. + /// Returns result of the last action. + pub(crate) fn internal_execute_actions( + &mut self, + account: &mut Account, + referral_id: &Option, + actions: &[Action], + prev_result: ActionResult, + user_id: &AccountId, + ) -> ActionResult { + let mut result = prev_result; + for action in actions { + result = self.internal_execute_action(account, referral_id, action, result, user_id); + } + result + } + + /// Executes single action on given account. Modifies passed account. Returns a result based on type of action. + pub(crate) fn internal_execute_action( + &mut self, + account: &mut Account, + referral_id: &Option, + action: &Action, + prev_result: ActionResult, + user_id: &AccountId, + ) -> ActionResult { + match action { + Action::Swap(swap_action) => { + let amount_in = swap_action + .amount_in + .map(|value| value.0) + .unwrap_or_else(|| prev_result.to_amount()); + + // handle token_in + match TokenType::parse_token(&swap_action.token_in) { + TokenType::Nep141 {token_id} => { + account.withdraw(&token_id, amount_in); + }, + TokenType::InnerMFT {pool_id} => { + let token_id = format!(":{}", pool_id); + self.internal_mft_transfer(token_id, user_id, &String::from(MFT_LOCKER), amount_in, None); + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + account.withdraw(&swap_action.token_in, amount_in); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } + + // do action + let amount_out = self.internal_pool_swap( + swap_action.pool_id, + &swap_action.token_in, + amount_in, + &swap_action.token_out, + swap_action.min_amount_out.0, + referral_id, + ); + + // handle token_out + match TokenType::parse_token(&swap_action.token_out) { + TokenType::Nep141 {token_id} => { + account.deposit(&token_id, amount_out); + }, + TokenType::InnerMFT {pool_id} => { + let token_id = format!(":{}", pool_id); + self.internal_mft_transfer(token_id, &String::from(MFT_LOCKER), user_id, amount_out, None); + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + account.deposit(&swap_action.token_out, amount_out); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } + + // [AUDIT_02] + ActionResult::Amount(U128(amount_out)) + } + } + } + + /// Swaps given amount_in of token_in into token_out via given pool. + /// Should be at least min_amount_out or swap will fail (prevents front running and other slippage issues). + pub(crate) fn internal_pool_swap( + &mut self, + pool_id: u64, + token_in: &AccountId, + amount_in: u128, + token_out: &AccountId, + min_amount_out: u128, + referral_id: &Option, + ) -> u128 { + let mut pool = self.pools.get(pool_id).expect("ERR_NO_POOL"); + let amount_out = pool.swap( + token_in, + amount_in, + token_out, + min_amount_out, + AdminFees { + exchange_fee: self.exchange_fee, + exchange_id: env::current_account_id(), + referral_fee: self.referral_fee, + referral_id: referral_id.clone(), + }, + ); + self.pools.replace(pool_id, &pool); + amount_out + } + + /// Executes set of actions on virtual account. + /// Returns amounts to send to the sender directly. + pub(crate) fn internal_direct_actions( + &mut self, + token_in: AccountId, + amount_in: Balance, + referral_id: Option, + actions: &[Action], + user_id: &AccountId, + ) -> Vec<(AccountId, Balance)> { + + // let @ be the virtual account + let mut account: Account = Account::new(&String::from(VIRTUAL_ACC)); + + match TokenType::parse_token(&token_in) { + TokenType::Nep141 {token_id} => { + account.deposit(&token_id, amount_in); + }, + TokenType::InnerMFT {pool_id: _} => { + // inner mft, already deposit, no action needed + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + account.deposit(&token_in, amount_in); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } + + let _ = self.internal_execute_actions( + &mut account, + &referral_id, + &actions, + ActionResult::Amount(U128(amount_in)), + user_id, + ); + + let mut result = vec![]; + for (token, amount) in account.tokens.to_vec() { + if amount > 0 { + result.push((token.clone(), amount)); + } + } + account.tokens.clear(); + + result + } +} diff --git a/ref-exchange/src/lib.rs b/ref-exchange/src/lib.rs index a053be3..6f7a97c 100644 --- a/ref-exchange/src/lib.rs +++ b/ref-exchange/src/lib.rs @@ -1,4 +1,5 @@ use std::convert::TryInto; +use std::collections::HashSet; use std::fmt; use near_contract_standards::storage_management::{ @@ -20,8 +21,9 @@ use crate::errors::*; use crate::admin_fee::AdminFees; use crate::pool::Pool; use crate::simple_pool::SimplePool; +use crate::utils::{check_token_duplicates, check_string_duplicates}; use crate::stable_swap::StableSwapPool; -use crate::utils::check_token_duplicates; +use crate::internals::*; pub use crate::views::{PoolInfo, ContractMetadata}; mod account_deposit; @@ -38,6 +40,7 @@ mod storage_impl; mod token_receiver; mod utils; mod views; +mod internals; near_sdk::setup_alloc!(); @@ -67,6 +70,7 @@ impl fmt::Display for RunningState { } } + #[near_bindgen] #[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)] pub struct Contract { @@ -110,6 +114,45 @@ impl Contract { pub fn add_simple_pool(&mut self, tokens: Vec, fee: u32) -> u64 { self.assert_contract_running(); check_token_duplicates(&tokens); + let token_ids: Vec = tokens.iter().map(|a| a.clone().into()).collect(); + self.internal_add_pool(Pool::SimplePool(SimplePool::new( + self.pools.len() as u32, + token_ids, + fee, + 0, + 0, + ))) + } + + /// Adds new "Simple Pool" with given uniform tokens and given fee. + /// enable uniform_token_id instead of only nep-141 token_id in field `tokens`, + /// limited to guardians access in case of unexpected use from misunderstanding + #[payable] + pub fn add_high_order_simple_pool(&mut self, tokens: Vec, fee: u32) -> u64 { + self.assert_contract_running(); + assert!(self.is_owner_or_guardians(), "ERR_NOT_ALLOWED"); + check_string_duplicates(&tokens); + let mut token_set = HashSet::<&String>::new(); + for token in &tokens { + token_set.insert(token); + + // check if token is in valid format + match TokenType::parse_token(token) { + TokenType::Nep141 {token_id: _} => { + // for nep 141 token, nothing need to verify + }, + TokenType::InnerMFT {pool_id} => { + if pool_id >= self.pools.len() { + env::panic("ERR_MFT_TOKEN_INNERID_INVALID".as_bytes()); + } + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + unimplemented!("OuterMFT not supported yet!"); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } + } + self.internal_add_pool(Pool::SimplePool(SimplePool::new( self.pools.len() as u32, tokens, @@ -174,7 +217,7 @@ impl Contract { } let referral_id = referral_id.map(|r| r.into()); let result = - self.internal_execute_actions(&mut account, &referral_id, &actions, ActionResult::None); + self.internal_execute_actions(&mut account, &referral_id, &actions, ActionResult::None, &sender_id); self.internal_save_account(&sender_id, account); result } @@ -215,6 +258,7 @@ impl Contract { let sender_id = env::predecessor_account_id(); let mut amounts: Vec = amounts.into_iter().map(|amount| amount.into()).collect(); let mut pool = self.pools.get(pool_id).expect("ERR_NO_POOL"); + // Add amounts given to liquidity first. It will return the balanced amounts. pool.add_liquidity( &sender_id, @@ -226,14 +270,27 @@ impl Contract { assert!(amount >= &min_amount.0, "ERR_MIN_AMOUNT"); } } + self.pools.replace(pool_id, &pool); + let mut deposits = self.internal_unwrap_or_default_account(&sender_id); let tokens = pool.tokens(); // Subtract updated amounts from deposits. This will fail if there is not enough funds for any of the tokens. for i in 0..tokens.len() { - deposits.withdraw(&tokens[i], amounts[i]); + match TokenType::parse_token(&tokens[i]) { + TokenType::Nep141 {token_id} => { + deposits.withdraw(&token_id, amounts[i]); + }, + TokenType::InnerMFT {pool_id} => { + let token_id = format!(":{}", pool_id); + self.internal_mft_transfer(token_id, &sender_id, &String::from(MFT_LOCKER), amounts[i], None); + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + deposits.withdraw(&tokens[i], amounts[i]); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } } self.internal_save_account(&sender_id, deposits); - self.pools.replace(pool_id, &pool); self.internal_check_storage(prev_storage); } @@ -285,6 +342,7 @@ impl Contract { self.assert_contract_running(); let prev_storage = env::storage_usage(); let sender_id = env::predecessor_account_id(); + let mut pool = self.pools.get(pool_id).expect("ERR_NO_POOL"); let amounts = pool.remove_liquidity( &sender_id, @@ -295,10 +353,24 @@ impl Contract { .collect(), ); self.pools.replace(pool_id, &pool); + let tokens = pool.tokens(); let mut deposits = self.internal_unwrap_or_default_account(&sender_id); for i in 0..tokens.len() { - deposits.deposit(&tokens[i], amounts[i]); + match TokenType::parse_token(&tokens[i]) { + TokenType::Nep141 {token_id} => { + deposits.deposit(&token_id, amounts[i]); + }, + TokenType::InnerMFT {pool_id} => { + let token_id = format!(":{}", pool_id); + self.internal_mft_transfer(token_id, &String::from(MFT_LOCKER), &sender_id, amounts[i], None); + }, + TokenType::OuterMFT {token_contract_id: _, inner_id: _} => { + deposits.deposit(&tokens[i], amounts[i]); + }, + TokenType::Illegal => {env::panic("ERR_TOKEN_INVALID".as_bytes());}, + } + } // Freed up storage balance from LP tokens will be returned to near_balance. if prev_storage > env::storage_usage() { @@ -350,123 +422,6 @@ impl Contract { } } -/// Internal methods implementation. -impl Contract { - - fn assert_contract_running(&self) { - match self.state { - RunningState::Running => (), - _ => env::panic(ERR51_CONTRACT_PAUSED.as_bytes()), - }; - } - - /// Check how much storage taken costs and refund the left over back. - fn internal_check_storage(&self, prev_storage: StorageUsage) { - let storage_cost = env::storage_usage() - .checked_sub(prev_storage) - .unwrap_or_default() as Balance - * env::storage_byte_cost(); - - let refund = env::attached_deposit() - .checked_sub(storage_cost) - .expect( - format!( - "ERR_STORAGE_DEPOSIT need {}, attatched {}", - storage_cost, env::attached_deposit() - ).as_str() - ); - if refund > 0 { - Promise::new(env::predecessor_account_id()).transfer(refund); - } - } - - /// Adds given pool to the list and returns it's id. - /// If there is not enough attached balance to cover storage, fails. - /// If too much attached - refunds it back. - fn internal_add_pool(&mut self, mut pool: Pool) -> u64 { - let prev_storage = env::storage_usage(); - let id = self.pools.len() as u64; - // exchange share was registered at creation time - pool.share_register(&env::current_account_id()); - self.pools.push(&pool); - self.internal_check_storage(prev_storage); - id - } - - /// Execute sequence of actions on given account. Modifies passed account. - /// Returns result of the last action. - fn internal_execute_actions( - &mut self, - account: &mut Account, - referral_id: &Option, - actions: &[Action], - prev_result: ActionResult, - ) -> ActionResult { - let mut result = prev_result; - for action in actions { - result = self.internal_execute_action(account, referral_id, action, result); - } - result - } - - /// Executes single action on given account. Modifies passed account. Returns a result based on type of action. - fn internal_execute_action( - &mut self, - account: &mut Account, - referral_id: &Option, - action: &Action, - prev_result: ActionResult, - ) -> ActionResult { - match action { - Action::Swap(swap_action) => { - let amount_in = swap_action - .amount_in - .map(|value| value.0) - .unwrap_or_else(|| prev_result.to_amount()); - account.withdraw(&swap_action.token_in, amount_in); - let amount_out = self.internal_pool_swap( - swap_action.pool_id, - &swap_action.token_in, - amount_in, - &swap_action.token_out, - swap_action.min_amount_out.0, - referral_id, - ); - account.deposit(&swap_action.token_out, amount_out); - // [AUDIT_02] - ActionResult::Amount(U128(amount_out)) - } - } - } - - /// Swaps given amount_in of token_in into token_out via given pool. - /// Should be at least min_amount_out or swap will fail (prevents front running and other slippage issues). - fn internal_pool_swap( - &mut self, - pool_id: u64, - token_in: &AccountId, - amount_in: u128, - token_out: &AccountId, - min_amount_out: u128, - referral_id: &Option, - ) -> u128 { - let mut pool = self.pools.get(pool_id).expect("ERR_NO_POOL"); - let amount_out = pool.swap( - token_in, - amount_in, - token_out, - min_amount_out, - AdminFees { - exchange_fee: self.exchange_fee, - exchange_id: env::current_account_id(), - referral_fee: self.referral_fee, - referral_id: referral_id.clone(), - }, - ); - self.pools.replace(pool_id, &pool); - amount_out - } -} #[cfg(test)] mod tests { diff --git a/ref-exchange/src/multi_fungible_token.rs b/ref-exchange/src/multi_fungible_token.rs index b9e836f..7daefb7 100644 --- a/ref-exchange/src/multi_fungible_token.rs +++ b/ref-exchange/src/multi_fungible_token.rs @@ -1,6 +1,6 @@ use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata; use near_sdk::json_types::{ValidAccountId, U128}; -use near_sdk::{ext_contract, near_bindgen, Balance, PromiseOrValue}; +use near_sdk::{ext_contract, near_bindgen, serde_json, Balance, PromiseOrValue}; use crate::utils::{GAS_FOR_FT_TRANSFER_CALL, GAS_FOR_RESOLVE_TRANSFER, NO_DEPOSIT}; use crate::*; @@ -58,7 +58,7 @@ fn parse_token_id(token_id: String) -> TokenOrPool { #[near_bindgen] impl Contract { - fn internal_mft_transfer( + pub(crate) fn internal_mft_transfer( &mut self, token_id: String, sender_id: &AccountId, @@ -74,7 +74,7 @@ impl Contract { pool.share_transfer(sender_id, receiver_id, amount); self.pools.replace(pool_id, &pool); log!( - "Transfer shares {} pool: {} from {} to {}", + "Transfer shares pool#{}: {} from {} to {}", pool_id, amount, sender_id, @@ -84,7 +84,7 @@ impl Contract { TokenOrPool::Token(token_id) => { let mut sender_account: Account = self.internal_unwrap_account(&sender_id); let mut receiver_account: Account = self.internal_unwrap_account(&receiver_id); - + sender_account.withdraw(&token_id, amount); receiver_account.deposit(&token_id, amount); self.internal_save_account(&sender_id, sender_account); @@ -148,6 +148,18 @@ impl Contract { } } + /// See if given account has registered the mft token or not + pub fn mft_is_registered(&self, token_id: String, account_id: ValidAccountId) -> bool { + match parse_token_id(token_id) { + TokenOrPool::Token(_) => env::panic(b"ERR_INVALID_TOKEN"), + TokenOrPool::Pool(pool_id) => self + .pools + .get(pool_id) + .expect("ERR_NO_POOL") + .share_is_registered(account_id.as_ref()), + } + } + /// Transfer one of internal tokens: LP or balances. /// `token_id` can either by account of the token or pool number. #[payable] @@ -181,32 +193,77 @@ impl Contract { assert_one_yocto(); self.assert_contract_running(); let sender_id = env::predecessor_account_id(); - self.internal_mft_transfer( - token_id.clone(), - &sender_id, - receiver_id.as_ref(), - amount.0, - memo, - ); - ext_share_token_receiver::mft_on_transfer( - token_id.clone(), - sender_id.clone(), - amount, - msg, - receiver_id.as_ref(), - NO_DEPOSIT, - env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL, - ) - .then(ext_self::mft_resolve_transfer( - token_id, - sender_id, - receiver_id.into(), - amount, - &env::current_account_id(), - NO_DEPOSIT, - GAS_FOR_RESOLVE_TRANSFER, - )) - .into() + + if receiver_id.as_ref() == &env::current_account_id() { + // if receiver is self, goes to possible instant swap process + match parse_token_id(token_id.clone()) { + TokenOrPool::Pool(pool_id) => { + let token_in = format!(":{}", pool_id); + if msg.is_empty() { + env::panic(ERR28_WRONG_MSG_FORMAT.as_bytes()); + } else { + // instant swap + let message = serde_json::from_str::(&msg) + .expect(ERR28_WRONG_MSG_FORMAT); + match message { + TokenReceiverMessage::Execute { + referral_id, + actions, + } => { + let referral_id = referral_id.map(|x| x.to_string()); + let out_amounts = self.internal_direct_actions( + token_in, + amount.0, + referral_id, + &actions, + &sender_id, + ); + for (token_out, amount_out) in out_amounts.into_iter() { + log!( + "Send to {}: with {} token of amount {}", + &sender_id, + &token_out, + amount_out + ); + self.internal_send_tokens(&sender_id, &token_out, amount_out); + } + // Even if send tokens fails, we don't return funds back to sender. + PromiseOrValue::Value(U128(0)) + } + } + } + } + TokenOrPool::Token(_) => env::panic("ERR_TOKEN_INVALID".as_bytes()), + } + } else { + // ordinary process + self.internal_mft_transfer( + token_id.clone(), + &sender_id, + receiver_id.as_ref(), + amount.0, + memo, + ); + ext_share_token_receiver::mft_on_transfer( + token_id.clone(), + sender_id.clone(), + amount, + msg, + receiver_id.as_ref(), + NO_DEPOSIT, + env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL, + ) + .then(ext_self::mft_resolve_transfer( + token_id, + sender_id, + receiver_id.into(), + amount, + &env::current_account_id(), + NO_DEPOSIT, + GAS_FOR_RESOLVE_TRANSFER, + )) + .into() + } } /// Returns how much was refunded back to the sender. @@ -235,7 +292,7 @@ impl Contract { let receiver_balance = self.internal_mft_balance(token_id.clone(), &receiver_id); if receiver_balance > 0 { let refund_amount = std::cmp::min(receiver_balance, unused_amount); - + let refund_to = if self.accounts.get(&sender_id).is_some() { sender_id } else { diff --git a/ref-exchange/src/pool.rs b/ref-exchange/src/pool.rs index 53d751f..4874ffd 100644 --- a/ref-exchange/src/pool.rs +++ b/ref-exchange/src/pool.rs @@ -212,4 +212,11 @@ impl Pool { Pool::StableSwapPool(pool) => pool.predict_remove_liquidity_by_tokens(amounts, fees), } } + + pub fn share_is_registered(&self, account_id: &AccountId) -> bool { + match self { + Pool::SimplePool(pool) => pool.share_is_registered(account_id), + Pool::StableSwapPool(pool) => pool.share_is_registered(account_id), + } + } } diff --git a/ref-exchange/src/simple_pool.rs b/ref-exchange/src/simple_pool.rs index 388aa96..621d53a 100644 --- a/ref-exchange/src/simple_pool.rs +++ b/ref-exchange/src/simple_pool.rs @@ -2,14 +2,10 @@ use std::cmp::min; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::collections::LookupMap; -use near_sdk::json_types::ValidAccountId; use near_sdk::{env, AccountId, Balance}; -use crate::StorageKey; +use crate::{StorageKey, MFT_LOCKER}; use crate::admin_fee::AdminFees; - -use crate::errors::{ - ERR13_LP_NOT_REGISTERED, ERR14_LP_ALREADY_REGISTERED, ERR31_ZERO_AMOUNT, ERR32_ZERO_SHARES, -}; +use crate::errors::*; use crate::utils::{ add_to_collection, integer_sqrt, SwapVolume, FEE_DIVISOR, INIT_SHARES_SUPPLY, U256, }; @@ -42,7 +38,7 @@ pub struct SimplePool { impl SimplePool { pub fn new( id: u32, - token_account_ids: Vec, + token_account_ids: Vec, total_fee: u32, exchange_fee: u32, referral_fee: u32, @@ -54,7 +50,7 @@ impl SimplePool { // [AUDIT_10] assert_eq!(token_account_ids.len(), NUM_TOKENS, "ERR_SHOULD_HAVE_2_TOKENS"); Self { - token_account_ids: token_account_ids.iter().map(|a| a.clone().into()).collect(), + token_account_ids: token_account_ids.clone(), amounts: vec![0u128; token_account_ids.len()], volumes: vec![SwapVolume::default(); token_account_ids.len()], total_fee, @@ -77,13 +73,23 @@ impl SimplePool { self.shares.insert(account_id, &0); } + pub fn share_is_registered(&self, account_id: &AccountId) -> bool { + self.shares.contains_key(account_id) + } + /// Transfers shares from predecessor to receiver. pub fn share_transfer(&mut self, sender_id: &AccountId, receiver_id: &AccountId, amount: u128) { - let balance = self.shares.get(&sender_id).expect("ERR_NO_SHARES"); + let balance = self.shares.get(&sender_id).expect(ERR13_LP_NOT_REGISTERED); if let Some(new_balance) = balance.checked_sub(amount) { self.shares.insert(&sender_id, &new_balance); } else { - env::panic(b"ERR_NOT_ENOUGH_SHARES"); + env::panic(ERR34_INSUFFICIENT_LP_SHARES.as_bytes()); + } + // if receiver is MFT_LOCKER and unregister, auto register + if receiver_id == MFT_LOCKER { + if self.shares.get(&receiver_id).is_none() { + self.shares.insert(&receiver_id, &0); + } } let balance_out = self .shares @@ -342,7 +348,7 @@ mod tests { let mut context = VMContextBuilder::new(); context.predecessor_account_id(accounts(0)); testing_env!(context.build()); - let mut pool = SimplePool::new(0, vec![accounts(1), accounts(2)], 30, 0, 0); + let mut pool = SimplePool::new(0, vec![accounts(1).into(), accounts(2).into()], 30, 0, 0); let mut amounts = vec![to_yocto("5"), to_yocto("10")]; let num_shares = pool.add_liquidity(accounts(0).as_ref(), &mut amounts); assert_eq!(amounts, vec![to_yocto("5"), to_yocto("10")]); @@ -392,7 +398,7 @@ mod tests { let mut context = VMContextBuilder::new(); context.predecessor_account_id(accounts(0)); testing_env!(context.build()); - let mut pool = SimplePool::new(0, vec![accounts(1), accounts(2)], 100, 100, 0); + let mut pool = SimplePool::new(0, vec![accounts(1).into(), accounts(2).into()], 100, 100, 0); let mut amounts = vec![to_yocto("5"), to_yocto("10")]; let num_shares = pool.add_liquidity(accounts(0).as_ref(), &mut amounts); assert_eq!(amounts, vec![to_yocto("5"), to_yocto("10")]); diff --git a/ref-exchange/src/stable_swap/mod.rs b/ref-exchange/src/stable_swap/mod.rs index 8bbc1ac..95e7a08 100644 --- a/ref-exchange/src/stable_swap/mod.rs +++ b/ref-exchange/src/stable_swap/mod.rs @@ -9,7 +9,7 @@ use crate::stable_swap::math::{ Fees, StableSwap, SwapResult, MAX_AMP, MAX_AMP_CHANGE, MIN_AMP, MIN_RAMP_DURATION, }; use crate::utils::{add_to_collection, SwapVolume, FEE_DIVISOR, U256}; -use crate::StorageKey; +use crate::{StorageKey, MFT_LOCKER}; mod math; @@ -626,6 +626,10 @@ impl StableSwapPool { self.shares.insert(account_id, &0); } + pub fn share_is_registered(&self, account_id: &AccountId) -> bool { + self.shares.contains_key(account_id) + } + /// Transfers shares from predecessor to receiver. pub fn share_transfer(&mut self, sender_id: &AccountId, receiver_id: &AccountId, amount: u128) { let balance = self.shares.get(&sender_id).expect(ERR13_LP_NOT_REGISTERED); @@ -634,6 +638,12 @@ impl StableSwapPool { } else { env::panic(ERR34_INSUFFICIENT_LP_SHARES.as_bytes()); } + // if receiver is MFT_LOCKER and unregister, auto register + if receiver_id == MFT_LOCKER { + if self.shares.get(&receiver_id).is_none() { + self.shares.insert(&receiver_id, &0); + } + } let balance_out = self .shares .get(&receiver_id) diff --git a/ref-exchange/src/token_receiver.rs b/ref-exchange/src/token_receiver.rs index 5fb6a42..a7bf165 100644 --- a/ref-exchange/src/token_receiver.rs +++ b/ref-exchange/src/token_receiver.rs @@ -1,59 +1,8 @@ use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; -use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{serde_json, PromiseOrValue}; - use crate::*; -pub const VIRTUAL_ACC: &str = "@"; - -/// Message parameters to receive via token function call. -#[derive(Serialize, Deserialize)] -#[serde(crate = "near_sdk::serde")] -#[serde(untagged)] -enum TokenReceiverMessage { - /// Alternative to deposit + execute actions call. - Execute { - referral_id: Option, - /// List of sequential actions. - actions: Vec, - }, -} - -impl Contract { - /// Executes set of actions on virtual account. - /// Returns amounts to send to the sender directly. - fn internal_direct_actions( - &mut self, - token_in: AccountId, - amount_in: Balance, - referral_id: Option, - actions: &[Action], - ) -> Vec<(AccountId, Balance)> { - - // let @ be the virtual account - let mut account: Account = Account::new(&String::from(VIRTUAL_ACC)); - - account.deposit(&token_in, amount_in); - let _ = self.internal_execute_actions( - &mut account, - &referral_id, - &actions, - ActionResult::Amount(U128(amount_in)), - ); - - let mut result = vec![]; - for (token, amount) in account.tokens.to_vec() { - if amount > 0 { - result.push((token.clone(), amount)); - } - } - account.tokens.clear(); - - result - } - -} #[near_bindgen] impl FungibleTokenReceiver for Contract { @@ -87,6 +36,7 @@ impl FungibleTokenReceiver for Contract { amount.0, referral_id, &actions, + sender_id.as_ref(), ); for (token_out, amount_out) in out_amounts.into_iter() { self.internal_send_tokens(sender_id.as_ref(), &token_out, amount_out); diff --git a/ref-exchange/src/utils.rs b/ref-exchange/src/utils.rs index 2ac9df8..9c1b61a 100644 --- a/ref-exchange/src/utils.rs +++ b/ref-exchange/src/utils.rs @@ -67,6 +67,12 @@ pub fn check_token_duplicates(tokens: &[ValidAccountId]) { assert_eq!(token_set.len(), tokens.len(), "ERR_TOKEN_DUPLICATES"); } +/// Checks if there are any duplicates in the given list of string. +pub fn check_string_duplicates(items: &[String]) { + let item_set: HashSet<_> = items.iter().collect(); + assert_eq!(item_set.len(), items.len(), "ERR_DUPLICATES"); +} + /// Newton's method of integer square root. pub fn integer_sqrt(value: U256) -> U256 { let mut guess: U256 = (value + U256::one()) >> 1; diff --git a/ref-exchange/tests/common/utils.rs b/ref-exchange/tests/common/utils.rs index 8b05628..776f3ad 100644 --- a/ref-exchange/tests/common/utils.rs +++ b/ref-exchange/tests/common/utils.rs @@ -124,6 +124,11 @@ pub fn get_version(pool: &ContractAccount) -> String { view!(pool.version()).unwrap_json::() } +/// get ref-exchange's mft_is_registered +pub fn get_mft_is_registered(pool: &ContractAccount, token_id: String, account_id: ValidAccountId) -> bool { + view!(pool.mft_is_registered(token_id, account_id)).unwrap_json::() +} + /// get ref-exchange's pool count pub fn get_num_of_pools(pool: &ContractAccount) -> u64 { view!(pool.get_number_of_pools()).unwrap_json::() @@ -238,6 +243,57 @@ pub fn to_va(a: AccountId) -> ValidAccountId { ValidAccountId::try_from(a).unwrap() } +pub fn pack_action( + pool_id: u32, + token_in: &str, + token_out: &str, + amount_in: Option, + min_amount_out: u128, +) -> String { + if let Some(amount_in) = amount_in { + format!( + "{{\"pool_id\": {}, \"token_in\": \"{}\", \"amount_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", + pool_id, token_in, amount_in, token_out, min_amount_out + ) + } else { + format!( + "{{\"pool_id\": {}, \"token_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", + pool_id, token_in, token_out, min_amount_out + ) + } +} + +pub fn direct_swap( + user: &UserAccount, + contract: &ContractAccount, + actions: Vec, +) -> ExecutionResult { + // {{\"pool_id\": 0, \"token_in\": \"dai\", \"token_out\": \"eth\", \"min_amount_out\": \"1\"}} + let actions_str = actions.join(", "); + let msg_str = format!("{{\"actions\": [{}]}}", actions_str); + // println!("{}", msg_str); + call!( + user, + contract.ft_transfer_call(to_va(swap()), to_yocto("1").into(), None, msg_str), + deposit = 1 + ) +} + +pub fn direct_swap_with_amount( + user: &UserAccount, + contract: &ContractAccount, + actions: Vec, + amount: u128, +) -> ExecutionResult { + let actions_str = actions.join(", "); + let msg_str = format!("{{\"actions\": [{}]}}", actions_str); + call!( + user, + contract.ft_transfer_call(to_va(swap()), amount.into(), None, msg_str), + deposit = 1 + ) +} + pub fn setup_pool_with_liquidity() -> ( UserAccount, UserAccount, @@ -497,4 +553,4 @@ pub fn deposit_token( ) .assert_success(); } -} \ No newline at end of file +} diff --git a/ref-exchange/tests/test_high_order.rs b/ref-exchange/tests/test_high_order.rs new file mode 100644 index 0000000..d2e8fba --- /dev/null +++ b/ref-exchange/tests/test_high_order.rs @@ -0,0 +1,297 @@ +use near_sdk::json_types::{U128}; +use near_sdk_sim::{call, to_yocto}; + +use ref_exchange::SwapAction; +use crate::common::utils::*; +pub mod common; + + +#[test] +fn high_order_liquidity() { + let ( + root, + owner, + pool, + token1, + _, + _ + ) = setup_pool_with_liquidity(); + assert_eq!(mft_balance_of(&pool, ":0", &root.account_id()), to_yocto("1")); + assert_eq!(mft_balance_of(&pool, ":1", &root.account_id()), to_yocto("1")); + assert_eq!(mft_balance_of(&pool, ":2", &root.account_id()), to_yocto("1")); + + // create high order pool + let out_come = call!( + owner, + pool.add_high_order_simple_pool(vec![token1.account_id(), ":0".to_string()], 25), + deposit = to_yocto("1") + ); + out_come.assert_success(); + // println!("{:#?}", out_come.promise_results()); + + // add liquidity + let out_come = call!( + root, + pool.add_liquidity(3, vec![U128(to_yocto("10")), U128(to_yocto("0.2"))], None), + deposit = to_yocto("0.0015") + ); + out_come.assert_success(); + assert_eq!(mft_balance_of(&pool, ":0", &root.account_id()), to_yocto("0.8")); + assert_eq!(mft_balance_of(&pool, ":3", &root.account_id()), to_yocto("1")); + assert_eq!(get_deposits(&pool, root.valid_account_id()).get(&dai()).unwrap().0, to_yocto("75")); + + // remove liqudity + let out_come = call!( + root, + pool.remove_liquidity(3, U128(to_yocto("0.5")), vec![U128(1), U128(1)]), + deposit = 1 + ); + out_come.assert_success(); + assert_eq!(mft_balance_of(&pool, ":0", &root.account_id()), to_yocto("0.9")); + assert_eq!(mft_balance_of(&pool, ":3", &root.account_id()), to_yocto("0.5")); + assert_eq!(get_deposits(&pool, root.valid_account_id()).get(&token1.account_id()).unwrap().0, to_yocto("80")); +} + +#[test] +fn high_order_ordinary_swap() { + let ( + root, + owner, + pool, + token1, + _, + _ + ) = setup_pool_with_liquidity(); + + // create high order pool + let out_come = call!( + owner, + pool.add_high_order_simple_pool(vec![token1.account_id(), ":0".to_string()], 25), + deposit = to_yocto("1") + ); + out_come.assert_success(); + + // add liquidity + let out_come = call!( + root, + pool.add_liquidity(3, vec![U128(to_yocto("10")), U128(to_yocto("1"))], None), + deposit = to_yocto("0.0015") + ); + out_come.assert_success(); + + // ordinary swap from old lp + let out_come = call!( + root, + pool.swap( + vec![SwapAction { + pool_id: 3, + token_in: token1.account_id(), + amount_in: Some(U128(to_yocto("1"))), + token_out: String::from(":0"), + min_amount_out: U128(1) + }], + None + ), + deposit = 1 + ); + out_come.assert_success(); + println!("swap logs: {:#?}", get_logs(&out_come)); + + let out_come = call!( + root, + pool.swap( + vec![SwapAction { + pool_id: 3, + token_in: String::from(":0"), + amount_in: Some(U128(90702432370993407592634)), + token_out: token1.account_id(), + min_amount_out: U128(1) + }], + None + ), + deposit = 1 + ); + out_come.assert_success(); + println!("swap logs: {:#?}", get_logs(&out_come)); + + // oridnary swap from a non lp + // prepare non lp + let new_user = root.create_user("new_user".to_string(), to_yocto("100")); + call!( + new_user, + token1.mint(to_va(new_user.account_id.clone()), U128(to_yocto("10"))) + ) + .assert_success(); + call!( + new_user, + pool.storage_deposit(None, Some(false)), + deposit = to_yocto("1") + ) + .assert_success(); + call!( + new_user, + token1.ft_transfer_call(to_va(swap()), to_yocto("10").into(), None, "".to_string()), + deposit = 1 + ) + .assert_success(); + // swap would fail cause not register on mft token + let out_come = call!( + new_user, + pool.swap( + vec![SwapAction { + pool_id: 3, + token_in: token1.account_id(), + amount_in: Some(U128(to_yocto("1"))), + token_out: String::from(":0"), + min_amount_out: U128(1) + }], + None + ), + deposit = 1 + ); + assert_eq!(get_error_count(&out_come), 1); + assert!(get_error_status(&out_come) + .contains("E13: LP not registered")); + assert_eq!(mft_balance_of(&pool, ":0", &new_user.account_id()), to_yocto("0")); + assert_eq!(mft_balance_of(&pool, ":3", &new_user.account_id()), to_yocto("0")); + assert_eq!(get_deposits(&pool, new_user.valid_account_id()).get(&token1.account_id()).unwrap().0, to_yocto("10")); + // register LP and then swap would succeed + assert_eq!(get_mft_is_registered(&pool, String::from(":0"), new_user.valid_account_id()), false); + let out_come = call!( + new_user, + pool.mft_register(String::from(":0"), new_user.valid_account_id()), + deposit = to_yocto("0.0008") + ); + out_come.assert_success(); + assert_eq!(get_mft_is_registered(&pool, String::from(":0"), new_user.valid_account_id()), true); + let out_come = call!( + new_user, + pool.swap( + vec![SwapAction { + pool_id: 3, + token_in: token1.account_id(), + amount_in: Some(U128(to_yocto("1"))), + token_out: String::from(":0"), + min_amount_out: U128(1) + }], + None + ), + deposit = 1 + ); + out_come.assert_success(); + println!("swap logs: {:#?}", get_logs(&out_come)); + assert_eq!(mft_balance_of(&pool, ":0", &new_user.account_id()), 90664988826115278572728); + assert_eq!(mft_balance_of(&pool, ":3", &new_user.account_id()), to_yocto("0")); + assert_eq!(get_deposits(&pool, new_user.valid_account_id()).get(&token1.account_id()).unwrap().0, to_yocto("9")); + +} + +#[test] +fn high_order_instant_swap() { + let ( + root, + owner, + pool, + token1, + _, + _ + ) = setup_pool_with_liquidity(); + + // create high order pool + let out_come = call!( + owner, + pool.add_high_order_simple_pool(vec![token1.account_id(), ":0".to_string()], 25), + deposit = to_yocto("1") + ); + out_come.assert_success(); + + // add liquidity + let out_come = call!( + root, + pool.add_liquidity(3, vec![U128(to_yocto("10")), U128(to_yocto("0.9"))], None), + deposit = to_yocto("0.0015") + ); + out_come.assert_success(); + + + let new_user = root.create_user("new_user".to_string(), to_yocto("100")); + call!( + new_user, + token1.mint(to_va(new_user.account_id.clone()), U128(to_yocto("10"))) + ) + .assert_success(); + // ft -> mft and without mft_register + // expected: ft_transfer_call revert + println!("Case 0101: ft swap mft but mft not registered"); + let action = pack_action(3, &token1.account_id(), &String::from(":0"), None, 1); + let out_come = direct_swap(&new_user, &token1, vec![action]); + out_come.assert_success(); + assert_eq!(get_error_count(&out_come), 1); + assert!(get_error_status(&out_come).contains("E13: LP not registered")); + assert_eq!(mft_balance_of(&pool, ":0", &new_user.account_id()), to_yocto("0")); + assert_eq!(mft_balance_of(&pool, ":3", &new_user.account_id()), to_yocto("0")); + assert_eq!(balance_of(&token1, &new_user.account_id()), to_yocto("10")); + + // register LP and then swap would succeed + println!("Case 0102: ft swap mft and mft has registered"); + assert_eq!(get_mft_is_registered(&pool, String::from(":0"), new_user.valid_account_id()), false); + let out_come = call!( + new_user, + pool.mft_register(String::from(":0"), new_user.valid_account_id()), + deposit = to_yocto("0.0008") + ); + out_come.assert_success(); + assert_eq!(get_mft_is_registered(&pool, String::from(":0"), new_user.valid_account_id()), true); + let action = pack_action(3, &token1.account_id(), &String::from(":0"), None, 1); + let out_come = direct_swap(&new_user, &token1, vec![action]); + out_come.assert_success(); + // println!("{:#?}", out_come.promise_results()); + assert_eq!(get_error_count(&out_come), 0); + assert_eq!(mft_balance_of(&pool, ":0", &new_user.account_id()), 81632189133894066833371); + assert_eq!(mft_balance_of(&pool, ":3", &new_user.account_id()), to_yocto("0")); + assert_eq!(balance_of(&token1, &new_user.account_id()), to_yocto("9")); + + println!("Case 0103: mft swap ft and ft has registered"); + let action = pack_action(3, &String::from(":0"), &token1.account_id(), None, 1); + let msg_str = format!("{{\"actions\": [{}]}}", action); + // println!("{}", msg_str); + let out_come = call!( + new_user, + pool.mft_transfer_call(String::from(":0"), pool.valid_account_id(), U128(81632189133894066833371), None, msg_str), + deposit = 1 + ); + out_come.assert_success(); + // println!("{:#?}", out_come.promise_results()); + assert_eq!(get_error_count(&out_come), 0); + assert_eq!(mft_balance_of(&pool, ":0", &new_user.account_id()), 0); + assert_eq!(mft_balance_of(&pool, ":3", &new_user.account_id()), to_yocto("0")); + assert_eq!(balance_of(&token1, &new_user.account_id()), to_yocto("9") + 995458165383034684495970); + + println!("Case 0104: mft swap ft and ft not registered"); + let new_user2 = root.create_user("new_user2".to_string(), to_yocto("10")); + call!( + new_user2, + pool.mft_register(String::from(":0"), new_user2.valid_account_id()), + deposit = to_yocto("0.0008") + ).assert_success(); + call!( + root, + pool.mft_transfer(String::from(":0"), new_user2.valid_account_id(), U128(to_yocto("0.1")), None), + deposit = 1 + ).assert_success(); + let action = pack_action(3, &String::from(":0"), &token1.account_id(), None, 1); + let msg_str = format!("{{\"actions\": [{}]}}", action); + // println!("{}", msg_str); + let out_come = call!( + new_user2, + pool.mft_transfer_call(String::from(":0"), pool.valid_account_id(), U128(to_yocto("0.01")), None, msg_str), + deposit = 1 + ); + out_come.assert_success(); + // println!("{:#?}", out_come.promise_results()); + assert_eq!(get_error_count(&out_come), 1); + assert!(get_error_status(&out_come).contains("The account new_user2 is not registered")); + assert_eq!(mft_balance_of(&pool, ":0", &new_user2.account_id()), to_yocto("0.09")); + assert_eq!(get_deposits(&pool, owner.valid_account_id()).get(&token1.account_id()).unwrap().0, 109668182972393998760573); +} + diff --git a/ref-exchange/tests/test_instant_swap.rs b/ref-exchange/tests/test_instant_swap.rs index 13937bc..9ddd8a2 100644 --- a/ref-exchange/tests/test_instant_swap.rs +++ b/ref-exchange/tests/test_instant_swap.rs @@ -1,49 +1,45 @@ use near_sdk::json_types::U128; -use near_sdk_sim::{ - call, to_yocto, ContractAccount, ExecutionResult, UserAccount, -}; - -use test_token::ContractContract as TestToken; +use near_sdk_sim::{call, to_yocto}; use crate::common::utils::*; pub mod common; -fn pack_action( - pool_id: u32, - token_in: &str, - token_out: &str, - amount_in: Option, - min_amount_out: u128, -) -> String { - if let Some(amount_in) = amount_in { - format!( - "{{\"pool_id\": {}, \"token_in\": \"{}\", \"amount_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", - pool_id, token_in, amount_in, token_out, min_amount_out - ) - } else { - format!( - "{{\"pool_id\": {}, \"token_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", - pool_id, token_in, token_out, min_amount_out - ) - } -} +// fn pack_action( +// pool_id: u32, +// token_in: &str, +// token_out: &str, +// amount_in: Option, +// min_amount_out: u128, +// ) -> String { +// if let Some(amount_in) = amount_in { +// format!( +// "{{\"pool_id\": {}, \"token_in\": \"{}\", \"amount_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", +// pool_id, token_in, amount_in, token_out, min_amount_out +// ) +// } else { +// format!( +// "{{\"pool_id\": {}, \"token_in\": \"{}\", \"token_out\": \"{}\", \"min_amount_out\": \"{}\"}}", +// pool_id, token_in, token_out, min_amount_out +// ) +// } +// } -fn direct_swap( - user: &UserAccount, - contract: &ContractAccount, - actions: Vec, - amount: u128, -) -> ExecutionResult { - // {{\"pool_id\": 0, \"token_in\": \"dai\", \"token_out\": \"eth\", \"min_amount_out\": \"1\"}} - let actions_str = actions.join(", "); - let msg_str = format!("{{\"actions\": [{}]}}", actions_str); - // println!("{}", msg_str); - call!( - user, - contract.ft_transfer_call(to_va(swap()), amount.into(), None, msg_str), - deposit = 1 - ) -} +// fn direct_swap( +// user: &UserAccount, +// contract: &ContractAccount, +// actions: Vec, +// amount: u128, +// ) -> ExecutionResult { +// // {{\"pool_id\": 0, \"token_in\": \"dai\", \"token_out\": \"eth\", \"min_amount_out\": \"1\"}} +// let actions_str = actions.join(", "); +// let msg_str = format!("{{\"actions\": [{}]}}", actions_str); +// // println!("{}", msg_str); +// call!( +// user, +// contract.ft_transfer_call(to_va(swap()), amount.into(), None, msg_str), +// deposit = 1 +// ) +// } #[test] fn instant_swap_scenario_01() { @@ -56,7 +52,7 @@ fn instant_swap_scenario_01() { .assert_success(); println!("Case 0101: wrong msg"); - let out_come = direct_swap(&new_user, &token1, vec!["wrong".to_string()], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec!["wrong".to_string()]); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 1); assert!(get_error_status(&out_come).contains("E28: Illegal msg in ft_transfer_call")); @@ -71,7 +67,7 @@ fn instant_swap_scenario_01() { None, to_yocto("1.9"), ); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 1); @@ -83,7 +79,7 @@ fn instant_swap_scenario_01() { println!("Case 0103: non-registered user swap but not registered in token2"); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 1); @@ -110,7 +106,7 @@ fn instant_swap_scenario_01() { println!("Case 0104: non-registered user swap"); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 0); // println!("{:#?}", out_come.promise_results()); @@ -153,7 +149,7 @@ fn instant_swap_scenario_02() { ); // println!("{:#?}", get_storage_balance(&pool, new_user.valid_account_id()).unwrap()); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); // println!("swap one logs: {:#?}", get_logs(&out_come)); // println!("{:#?}", out_come.promise_results()); @@ -200,7 +196,7 @@ fn instant_swap_scenario_02() { assert_eq!(balance_of(&token1, &new_user.account_id), to_yocto("9")); assert_eq!(balance_of(&token2, &new_user.account_id), to_yocto("10")); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); // println!("total logs: {:#?}", get_logs(&out_come)); @@ -256,7 +252,7 @@ fn instant_swap_scenario_02() { to_yocto("5") ); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action]); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 0); assert_eq!( @@ -283,7 +279,7 @@ fn instant_swap_scenario_02() { ) .assert_success(); let action = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token3, vec![action], to_yocto("1")); + let out_come = direct_swap(&new_user, &token3, vec![action]); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 1); // println!("{}", get_error_status(&out_come)); @@ -346,7 +342,7 @@ fn instant_swap_scenario_03() { println!("Case 0301: two actions with one output token"); let action1 = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); let action2 = pack_action(1, &token2.account_id(), &token3.account_id(), None, 1); - let out_come = direct_swap(&new_user, &token1, vec![action1, action2], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action1, action2]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 0); @@ -359,7 +355,7 @@ fn instant_swap_scenario_03() { println!("Case 0302: two actions with tow output token"); let action1 = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); let action2 = pack_action(1, &token2.account_id(), &token3.account_id(), Some(to_yocto("1")), 1); - let out_come = direct_swap(&new_user, &token1, vec![action1, action2], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action1, action2]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 0); @@ -375,7 +371,7 @@ fn instant_swap_scenario_03() { assert!(!is_register_to_token(&token2, new_user.valid_account_id())); let action1 = pack_action(0, &token1.account_id(), &token2.account_id(), None, 1); let action2 = pack_action(1, &token2.account_id(), &token3.account_id(), Some(to_yocto("1")), 1); - let out_come = direct_swap(&new_user, &token1, vec![action1, action2], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action1, action2]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 1); @@ -409,7 +405,7 @@ fn instant_swap_scenario_03() { Some(to_yocto("1")), 1, ); - let out_come = direct_swap(&new_user, &token1, vec![action1, action2], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action1, action2]); out_come.assert_success(); // println!("{:#?}", out_come.promise_results()); assert_eq!(get_error_count(&out_come), 1); @@ -435,7 +431,7 @@ fn instant_swap_scenario_03() { Some(to_yocto("1.2")), 1, ); - let out_come = direct_swap(&new_user, &token1, vec![action1, action2], to_yocto("1")); + let out_come = direct_swap(&new_user, &token1, vec![action1, action2]); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 1); // println!("{}", get_error_status(&out_come)); @@ -464,7 +460,7 @@ fn instant_swap_scenario_04() { println!("Case 0401: non-registered user stable swap but not registered in token2"); let action = pack_action(0, &tokens[0].account_id(), &tokens[1].account_id(), None, 1); - let out_come = direct_swap(&user, &tokens[0], vec![action], 1*ONE_DAI); + let out_come = direct_swap_with_amount(&user, &tokens[0], vec![action], 1*ONE_DAI); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 1); assert!(get_error_status(&out_come) @@ -488,7 +484,7 @@ fn instant_swap_scenario_04() { ) .assert_success(); let action = pack_action(0, &tokens[0].account_id(), &tokens[1].account_id(), None, 1); - let out_come = direct_swap(&user, &tokens[0], vec![action], 1*ONE_DAI); + let out_come = direct_swap_with_amount(&user, &tokens[0], vec![action], 1*ONE_DAI); out_come.assert_success(); assert_eq!(get_error_count(&out_come), 0); assert!(get_storage_balance(&pool, user.valid_account_id()).is_none()); diff --git a/ref-exchange/tests/test_migrate.rs b/ref-exchange/tests/test_migrate.rs index 87e1028..318dc95 100644 --- a/ref-exchange/tests/test_migrate.rs +++ b/ref-exchange/tests/test_migrate.rs @@ -46,7 +46,7 @@ fn test_upgrade() { .assert_success(); let metadata = get_metadata(&pool); // println!("{:#?}", metadata); - assert_eq!(metadata.version, "1.4.1".to_string()); + assert_eq!(metadata.version, "1.4.2".to_string()); assert_eq!(metadata.exchange_fee, 1600); assert_eq!(metadata.referral_fee, 400); assert_eq!(metadata.state, RunningState::Running); diff --git a/res/ref_exchange_release.wasm b/res/ref_exchange_release.wasm index 65609b2..6d7d5cf 100755 Binary files a/res/ref_exchange_release.wasm and b/res/ref_exchange_release.wasm differ diff --git a/test-token/src/lib.rs b/test-token/src/lib.rs index bb438fc..5ea935e 100644 --- a/test-token/src/lib.rs +++ b/test-token/src/lib.rs @@ -4,7 +4,7 @@ use near_contract_standards::fungible_token::metadata::{ use near_contract_standards::fungible_token::FungibleToken; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::json_types::{ValidAccountId, U128}; -use near_sdk::{env, near_bindgen, AccountId, PanicOnDefault, PromiseOrValue}; +use near_sdk::{near_bindgen, AccountId, PanicOnDefault, PromiseOrValue}; near_sdk::setup_alloc!();