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

[MEX-591] copy farm #987

Draft
wants to merge 1 commit into
base: impl-deployer-farms-with-top-up
Choose a base branch
from
Draft
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
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ members = [
"dex",
"dex/farm",
"dex/farm/meta",
"dex/farm-with-top-up",
"dex/farm-with-top-up/meta",
"dex/farm-with-locked-rewards",
"dex/farm-with-locked-rewards/meta",
"dex/pair",
Expand Down
53 changes: 28 additions & 25 deletions common/modules/farm/farm_base_impl/src/base_traits_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,35 +73,38 @@ pub trait FarmContract {
) -> BigUint<<Self::FarmSc as ContractBase>::Api> {
let current_block_nonce = sc.blockchain().get_block_nonce();
let last_reward_nonce = sc.last_reward_block_nonce().get();
if current_block_nonce > last_reward_nonce {
let to_mint =
Self::calculate_per_block_rewards(sc, current_block_nonce, last_reward_nonce);
if to_mint != 0 {
Self::mint_rewards(sc, token_id, &to_mint);
}

sc.last_reward_block_nonce().set(current_block_nonce);

to_mint
} else {
BigUint::zero()
if current_block_nonce <= last_reward_nonce {
return BigUint::zero();
}

let to_mint = Self::calculate_per_block_rewards(sc, current_block_nonce, last_reward_nonce);
if to_mint != 0 {
Self::mint_rewards(sc, token_id, &to_mint);
}

sc.last_reward_block_nonce().set(current_block_nonce);

to_mint
}

fn generate_aggregated_rewards(
sc: &Self::FarmSc,
storage_cache: &mut StorageCache<Self::FarmSc>,
) {
let total_reward = Self::mint_per_block_rewards(sc, &storage_cache.reward_token_id);
if total_reward > 0u64 {
storage_cache.reward_reserve += &total_reward;

if storage_cache.farm_token_supply != 0u64 {
let increase = (&total_reward * &storage_cache.division_safety_constant)
/ &storage_cache.farm_token_supply;
storage_cache.reward_per_share += &increase;
}
if total_reward == 0 {
return;
}

storage_cache.reward_reserve += &total_reward;

if storage_cache.farm_token_supply == 0 {
return;
}

let increase = (&total_reward * &storage_cache.division_safety_constant)
/ &storage_cache.farm_token_supply;
storage_cache.reward_per_share += &increase;
}

fn calculate_rewards(
Expand All @@ -112,12 +115,12 @@ pub trait FarmContract {
storage_cache: &StorageCache<Self::FarmSc>,
) -> BigUint<<Self::FarmSc as ContractBase>::Api> {
let token_rps = token_attributes.get_reward_per_share();
if storage_cache.reward_per_share > token_rps {
let rps_diff = &storage_cache.reward_per_share - &token_rps;
farm_token_amount * &rps_diff / &storage_cache.division_safety_constant
} else {
BigUint::zero()
if storage_cache.reward_per_share <= token_rps {
return BigUint::zero();
}

let rps_diff = &storage_cache.reward_per_share - &token_rps;
farm_token_amount * &rps_diff / &storage_cache.division_safety_constant
}

fn create_enter_farm_initial_attributes(
Expand Down
100 changes: 100 additions & 0 deletions dex/farm-with-top-up/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
[package]
name = "farm-with-top-up"
version = "0.0.0"
authors = ["MultiversX <[email protected]>"]
edition = "2021"
publish = false

[lib]
path = "src/lib.rs"

[dependencies.farm_base_impl]
path = "../../common/modules/farm/farm_base_impl"

[dependencies.config]
path = "../../common/modules/farm/config"

[dependencies.farm_token]
path = "../../common/modules/farm/farm_token"

[dependencies.rewards]
path = "../../common/modules/farm/rewards"

[dependencies.events]
path = "../../common/modules/farm/events"

[dependencies.contexts]
path = "../../common/modules/farm/contexts"

[dependencies.utils]
path = "../../common/modules/utils"

[dependencies.pausable]
path = "../../common/modules/pausable"

[dependencies.permissions_module]
path = "../../common/modules/permissions_module"

[dependencies.permissions_hub_module]
path = "../../common/modules/permissions_hub_module"

[dependencies.original_owner_helper]
path = "../../common/modules/original_owner_helper"

[dependencies.sc_whitelist_module]
path = "../../common/modules/sc_whitelist_module"

[dependencies.pair]
path = "../pair"

[dependencies.common_structs]
path = "../../common/common_structs"

[dependencies.common_errors]
path = "../../common/common_errors"

[dependencies.mergeable]
path = "../../common/traits/mergeable"

[dependencies.fixed-supply-token]
path = "../../common/traits/fixed-supply-token"

[dependencies.farm-boosted-yields]
path = "../../energy-integration/farm-boosted-yields"

[dependencies.week-timekeeping]
path = "../../energy-integration/common-modules/week-timekeeping"

[dependencies.weekly-rewards-splitting]
path = "../../energy-integration/common-modules/weekly-rewards-splitting"

[dependencies.energy-query]
path = "../../energy-integration/common-modules/energy-query"

[dependencies.permissions-hub]
path = "../permissions-hub"

[dependencies.multiversx-sc]
version = "=0.53.2"
features = ["esdt-token-payment-legacy-decode"]

[dependencies.multiversx-sc-modules]
version = "=0.53.2"

[dev-dependencies]
num-bigint = "0.4.2"

[dev-dependencies.energy-update]
path = "../../energy-integration/energy-update"

[dev-dependencies.multiversx-sc-scenario]
version = "=0.53.2"

[dev-dependencies.energy-factory-mock]
path = "../../energy-integration/energy-factory-mock"

[dev-dependencies.simple-lock]
path = "../../locked-asset/simple-lock"

[dev-dependencies.timestamp-oracle]
path = "../../energy-integration/timestamp-oracle"
105 changes: 105 additions & 0 deletions dex/farm-with-top-up/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Farm Smart Contract

## Abstract

Liquidity providers of xExchange are incentivized with MEX rewards in exchange for them locking their LP tokens in Farm contracts.

## Introduction

This smart contract has the role of generating and distributing MEX tokens to liquidity providers that choose to lock their LP tokens, thus increasing the ecosystem stability.

## Endpoints

### init

```rust
#[init]
fn init(
&self,
reward_token_id: TokenIdentifier,
farming_token_id: TokenIdentifier,
division_safety_constant: BigUint,
pair_contract_address: ManagedAddress,
);
```

The arguments are:

- __reward_token_id__ - MEX token ID
- __farming_token_id__ - token used for farming - LP tokens usually
- __division_safety_constant__ - a constant that is used for math safety functions - increasing precision of reward distribution
- __pair_contract_address__ - almost each farm contract has an associated pair contract, exception being the MEX farm. This address needs to be known because in case of penalty burn, the farm will need the Pair contract in order to convert LP tokens to MEX and then burn them

### enterFarm

```rust
#[payable("*")]
#[endpoint(enterFarm)]
fn enter_farm(&self);
```

This endpoint receives at least one payment:

- The first payment has to be of type __farming_token_id__. The actual token that is meant to be locked inside the Farm contract.
- The additional payments, if any, will be Farm positions and will be used to be merged with the newly created position, in order to consolidate all previous positions with the current one.

This endpoint will give back to the caller a Farm position as a result. The Farm position is a META esdt that contains, in its attributes, information about the user input tokens and the current state of the contract when the user did enter. This information will be later used when trying to claim rewards or exit farm.

### exitFarm

```rust
#[payable("*")]
#[endpoint(exitFarm)]
fn exit_farm(&self);
```

This endpoint receives one payment, and that is the Farm Position. Based on an internal counter that the contract keeps track of, which is the __rps__ - meaning reward per share, the contract can calculate the reward that it needs to return to the caller for those specific tokens that he has sent. The output will consist of two payments: the LP tokens initially added and the accumulated rewards.

This contract simulates per-block-reward-generation by keeping track of the last block that generated mex and keeps updating on every endpoint execution. Everytime an execution happens, the contract will generate the rewards for previous blocks. This is the case for the first successful TX inside a block, so only once per block this check has to be made and the action to be taken.

If a user decides to exit too early, they will receive a penalty. This contract will take a part of its input LP tokens and will used them to buyback-and-burn MEX. This is done via a smart contract call to the configured pair contract address, via __removeLiquidityAndBuybackAndBurnToken__ endpoint.

### claimRewards

```rust
#[payable("*")]
#[endpoint(claimRewards)]
fn claim_rewards(&self);
```

This endpoint receives at least one payment:

- The first payment is a Farm position that is 'harvested'. So for this position, the contract will calculate the reward and will return it to its caller. The contract will create a new position that has the ```RPS`` (Reward per share) reset.
- The additional payments, if any, will be other Farm positions and will be used to be merged with the newly created one.

### compoundRewards

```rust
#[payable("*")]
#[endpoint(compoundRewards)]
fn compound_rewards(&self);
```

This endpoint is similar with claimRewards, the differences being that instead of giving back the rewards to the caller, they are compounded into the newly created position (with the reset RPS). For this to be possible, reward token and farming token have to be the same, hence it is applicable only in case of MEX Farm.

### mergePositions

```rust
#[payable("*")]
#[endpoint(mergeFarmTokens)]
fn merge_farm_tokens(&self);
```

This endpoint merges two or more farm positions together and returns a single consolidated position to the caller.

## Testing

Aside from the scenario tests, there are a lot of tests that are available in the rust test suite.

## Interaction

The interaction scripts for this contract are located in the dex subdirectory of the root project directory.

## Deployment

The deployment of this contract is done using interaction scripts and it is managed by its admin (regular wallet at the moment, yet soon to be governance smart contract).
13 changes: 13 additions & 0 deletions dex/farm-with-top-up/meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "farm-with-top-up-abi"
version = "0.0.0"
authors = ["MultiversX <[email protected]>"]
edition = "2021"
publish = false

[dependencies.farm-with-top-up]
path = ".."

[dependencies.multiversx-sc-meta-lib]
version = "0.53.2"
default-features = false
3 changes: 3 additions & 0 deletions dex/farm-with-top-up/meta/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
multiversx_sc_meta_lib::cli_main::<farm_with_top_up::AbiProvider>();
}
3 changes: 3 additions & 0 deletions dex/farm-with-top-up/multiversx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "rust"
}
Loading