Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New farm refactor #59

Merged
merged 3 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 48 additions & 21 deletions elrond_dex_farm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ elrond_wasm::derive_imports!();

type Epoch = u64;
type Nonce = u64;
const PENALTY_PERCENT: u64 = 10;
const BURN_TOKENS_GAS_LIMIT: u64 = 5000000;
const EXIT_FARM_MIN_EPOCHS: u64 = 3;
const EXIT_FARM_NO_PENALTY_MIN_EPOCHS: u64 = 3;

mod config;
mod liquidity_pool;
Expand Down Expand Up @@ -107,34 +108,40 @@ pub trait Farm:
#[payment_token] payment_token_id: TokenIdentifier,
#[payment] liquidity: Self::BigUint,
) -> SCResult<ExitFarmResultType<Self::BigUint>> {
//require!(self.is_active(), "Not active");
require!(!self.farm_token_id().is_empty(), "No issued farm token");
let token_nonce = self.call_value().esdt_token_nonce();
let farm_token_id = self.farm_token_id().get();
require!(payment_token_id == farm_token_id, "Bad input token");

let farm_attributes = self.get_farm_attributes(&payment_token_id, token_nonce)?;
let enter_amount = &farm_attributes.total_entering_amount * &liquidity
let mut enter_amount = &farm_attributes.total_entering_amount * &liquidity
/ farm_attributes.total_liquidity_amount.clone();
require!(
farm_attributes.entering_epoch + EXIT_FARM_MIN_EPOCHS
< self.blockchain().get_block_epoch(),
"Exit too soon"
);
require!(enter_amount > 0, "Cannot exit farm with 0 entering amount");

// Before removing liquidity, first generate the rewards.
let reward_token_id = self.reward_token_id().get();
self.increase_actual_reserves(&self.mint_rewards(&reward_token_id));

let caller = self.blockchain().get_caller();
let reward = self.remove_liquidity(&liquidity, &enter_amount)?;
let mut reward = self.remove_liquidity(&liquidity, &enter_amount)?;
let farming_token_id = self.farming_token_id().get();
let reward_token_id = self.reward_token_id().get();
self.send().burn_tokens(
&farm_token_id,
token_nonce,
&liquidity,
BURN_TOKENS_GAS_LIMIT,
);

self.mint_rewards(&reward_token_id);
if self.should_apply_penalty(farm_attributes.entering_epoch) {
let mut penalty_amount = self.get_penalty_amount(reward.clone());
self.send().burn_tokens(&reward_token_id, 0, &penalty_amount, BURN_TOKENS_GAS_LIMIT);
reward -= penalty_amount;

penalty_amount = self.get_penalty_amount(enter_amount.clone());
self.send().burn_tokens(&farming_token_id, 0, &penalty_amount, BURN_TOKENS_GAS_LIMIT);
enter_amount -= penalty_amount;
}

self.send()
.transfer_tokens(&farming_token_id, 0, &enter_amount, &caller);
self.send()
Expand Down Expand Up @@ -175,6 +182,10 @@ pub trait Farm:
"Cannot exit farm with 0 entering amount"
);

// Before removing liquidity, first generate the rewards.
let reward_token_id = self.reward_token_id().get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here was a logical issue, not ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a unit test ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a logical issue. There is a test for this, but tests reside inside arwen repo, at least atm

self.increase_actual_reserves(&self.mint_rewards(&reward_token_id));

// Remove liquidity and burn the received SFT.
let reward = self.remove_liquidity(&liquidity, &entering_amount)?;
self.send().burn_tokens(
Expand All @@ -197,9 +208,7 @@ pub trait Farm:
self.send()
.transfer_tokens(&farm_token_id, new_nonce, &re_added_liquidity, &caller);

// Finally, mint and send the rewards.
let reward_token_id = self.reward_token_id().get();
self.mint_rewards(&reward_token_id);
// Send rewards
self.send()
.transfer_tokens(&reward_token_id, 0, &reward, &caller);

Expand All @@ -222,10 +231,11 @@ pub trait Farm:
fn acceptFee(
&self,
#[payment_token] token_in: TokenIdentifier,
#[payment] _amount: Self::BigUint,
#[payment] amount: Self::BigUint,
) -> SCResult<()> {
let reward_token_id = self.reward_token_id().get();
require!(token_in == reward_token_id, "Bad fee token identifier");
self.increase_actual_reserves(&amount);
Ok(())
}

Expand All @@ -236,8 +246,8 @@ pub trait Farm:
attributes_raw: BoxedBytes,
) -> SCResult<Self::BigUint> {
require!(liquidity > 0, "Zero liquidity input");
let total_supply = self.total_supply().get();
require!(total_supply > liquidity, "Not enough supply");
let farm_token_supply = self.farm_token_supply().get();
require!(farm_token_supply > liquidity, "Not enough supply");

let attributes = self.decode_attributes(&attributes_raw)?;
require!(
Expand All @@ -247,13 +257,20 @@ pub trait Farm:

let entering_amount =
&attributes.total_entering_amount * &liquidity / attributes.total_liquidity_amount;
Ok(self.calculate_reward_for_given_liquidity(

let reward = self.calculate_reward_for_given_liquidity(
&liquidity,
&entering_amount,
&total_supply,
&farm_token_supply,
&self.virtual_reserves().get(),
&self.reward_token_id().get(),
))
&self.actual_reserves().get(),
);

if self.should_apply_penalty(attributes.entering_epoch) {
Ok(&reward - &self.get_penalty_amount(reward.clone()))
} else {
Ok(reward)
}
}

#[view(decodeAttributes)]
Expand Down Expand Up @@ -448,6 +465,16 @@ pub trait Farm:
new_nonce
}

#[inline]
fn should_apply_penalty(&self, entering_epoch: Epoch) -> bool {
entering_epoch + EXIT_FARM_NO_PENALTY_MIN_EPOCHS > self.blockchain().get_block_epoch()
}

#[inline]
fn get_penalty_amount(&self, amount: Self::BigUint) -> Self::BigUint {
amount * Self::BigUint::from(PENALTY_PERCENT) / Self::BigUint::from(100u64)
}

#[view(getFarmTokenId)]
#[storage_mapper("farm_token_id")]
fn farm_token_id(&self) -> SingleValueMapper<Self::Storage, TokenIdentifier>;
Expand Down
89 changes: 42 additions & 47 deletions elrond_dex_farm/src/liquidity_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,27 @@ use super::rewards;
pub trait LiquidityPoolModule: rewards::RewardsModule + config::ConfigModule {
fn add_liquidity(&self, amount: &Self::BigUint) -> SCResult<Self::BigUint> {
require!(amount > &0, "Amount needs to be greater than 0");
let reward_token_id = self.reward_token_id().get();
let farming_token_id = self.farming_token_id().get();
let mut total_supply = self.total_supply().get();
let mut farm_token_supply = self.farm_token_supply().get();
let mut virtual_reserves = self.virtual_reserves().get();

let liquidity = self.calculate_liquidity(
amount,
&total_supply,
&farm_token_supply,
&virtual_reserves,
&farming_token_id,
&reward_token_id,
);
require!(liquidity > 0, "Insuficient liquidity minted");

if total_supply == 0 {
if farm_token_supply == 0 {
require!(
liquidity > MINIMUM_INITIAL_FARM_AMOUNT,
"First farm needs to be greater than minimum amount"
);
}
total_supply += &liquidity;
self.total_supply().set(&total_supply);
farm_token_supply += &liquidity;
self.farm_token_supply().set(&farm_token_supply);

if farming_token_id != reward_token_id {
virtual_reserves += amount;
self.virtual_reserves().set(&virtual_reserves);
}
virtual_reserves += amount;
self.virtual_reserves().set(&virtual_reserves);

Ok(liquidity)
}
Expand All @@ -49,75 +43,76 @@ pub trait LiquidityPoolModule: rewards::RewardsModule + config::ConfigModule {
require!(liquidity > &0, "Amount needs to be greater than 0");
require!(enter_amount > &0, "Amount needs to be greater than 0");

let reward_token_id = self.reward_token_id().get();
let farming_token_id = self.farming_token_id().get();
let mut virtual_reserves = self.virtual_reserves().get();
let mut total_supply = self.total_supply().get();
require!(&total_supply > liquidity, "Not enough supply");

let mut farm_token_supply = self.farm_token_supply().get();
let mut actual_reserves = self.actual_reserves().get();
let reward = self.calculate_reward_for_given_liquidity(
&liquidity,
&enter_amount,
&total_supply,
&farm_token_supply,
&virtual_reserves,
&reward_token_id,
&actual_reserves,
);

total_supply -= liquidity;
self.total_supply().set(&total_supply);
//These are sanity checks. Should never fail.
require!(&farm_token_supply > liquidity, "Not enough supply");
require!(&virtual_reserves > enter_amount, "Not enough virtual amount");
require!(actual_reserves >= reward, "Not enough actual reserves");

if farming_token_id != reward_token_id {
require!(
&virtual_reserves > enter_amount,
"Virtual amount is less than enter amount"
);
virtual_reserves -= enter_amount;
self.virtual_reserves().set(&virtual_reserves);
}
actual_reserves -= &reward;
self.actual_reserves().set(&actual_reserves);

farm_token_supply -= liquidity;
self.farm_token_supply().set(&farm_token_supply);

virtual_reserves -= enter_amount;
self.virtual_reserves().set(&virtual_reserves);

Ok(reward)
}

fn calculate_liquidity(
&self,
amount: &Self::BigUint,
total_supply: &Self::BigUint,
farm_token_supply: &Self::BigUint,
virtual_reserves: &Self::BigUint,
farming_token_id: &TokenIdentifier,
reward_token_id: &TokenIdentifier,
) -> Self::BigUint {
let mut actual_reserves = self.blockchain().get_esdt_balance(
&self.blockchain().get_sc_address(),
reward_token_id.as_esdt_identifier(),
0,
);
let actual_reserves = self.actual_reserves().get();
let reward_amount = self.calculate_reward_amount_current_block();

if farming_token_id == reward_token_id {
actual_reserves -= amount;
}

if total_supply == &0 {
if farm_token_supply == &0 {
amount.clone()
} else {
let total_reserves = virtual_reserves + &actual_reserves + reward_amount;
amount * &total_supply / total_reserves
amount * &farm_token_supply / total_reserves
}
}

fn is_first_provider(&self) -> bool {
self.total_supply().get() == 0
self.farm_token_supply().get() == 0
}

fn minimum_liquidity_farm_amount(&self) -> u64 {
MINIMUM_INITIAL_FARM_AMOUNT
}

fn increase_actual_reserves(&self, amount: &Self::BigUint) {
if amount > &0 {
let mut actual_reserves = self.actual_reserves().get();
actual_reserves += amount;
self.actual_reserves().set(&actual_reserves);
}
}

#[view(getTotalSupply)]
#[storage_mapper("total_supply")]
fn total_supply(&self) -> SingleValueMapper<Self::Storage, Self::BigUint>;
#[storage_mapper("farm_token_supply")]
fn farm_token_supply(&self) -> SingleValueMapper<Self::Storage, Self::BigUint>;

#[view(getVirtualReserves)]
#[storage_mapper("virtual_reserves")]
fn virtual_reserves(&self) -> SingleValueMapper<Self::Storage, Self::BigUint>;

#[view(getActualReserves)]
#[storage_mapper("actual_reserves")]
fn actual_reserves(&self) -> SingleValueMapper<Self::Storage, Self::BigUint>;
}
16 changes: 6 additions & 10 deletions elrond_dex_farm/src/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub trait RewardsModule {
}
}

fn mint_rewards(&self, token_id: &TokenIdentifier) {
fn mint_rewards(&self, token_id: &TokenIdentifier) -> Self::BigUint {
let current_nonce = self.blockchain().get_block_nonce();
let to_mint = self.calculate_reward_amount(current_nonce);
if to_mint != 0 {
Expand All @@ -43,27 +43,23 @@ pub trait RewardsModule {
);
self.last_reward_block_nonce().set(&current_nonce);
}
to_mint
}

fn calculate_reward_for_given_liquidity(
&self,
liquidity: &Self::BigUint,
enter_amount: &Self::BigUint,
total_supply: &Self::BigUint,
farm_token_supply: &Self::BigUint,
virtual_reserves: &Self::BigUint,
reward_token_id: &TokenIdentifier,
actual_reserves: &Self::BigUint,
) -> Self::BigUint {
let actual_reserves = self.blockchain().get_esdt_balance(
&self.blockchain().get_sc_address(),
reward_token_id.as_esdt_identifier(),
0,
);
let big_zero = Self::BigUint::zero();
let reward_amount = self.calculate_reward_amount_current_block();
let total_reserves = virtual_reserves + &actual_reserves + reward_amount;

let worth = if total_supply > &0 {
liquidity * &total_reserves / total_supply.clone()
let worth = if farm_token_supply > &0 {
liquidity * &total_reserves / farm_token_supply.clone()
} else {
big_zero.clone()
};
Expand Down
8 changes: 8 additions & 0 deletions multi_token_farm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ pub struct FarmTokenAttributes<BigUint: BigUintApi> {
entering_epoch: Epoch,
}

/*
This contract is used at the moment and might not be up to date.
TODOs:
-> remove commented lines
-> remove the +1 when creating farm tokens
-> change calculate_rewards_for_given_position so it receives token attributes
*/

#[elrond_wasm_derive::contract]
pub trait Farm: liquidity_pool::LiquidityPoolModule + rewards::RewardsModule {
#[proxy]
Expand Down