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

Add legacy SC: farm staking proxy v1.3 #927

Closed
wants to merge 7 commits into from
Closed
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
25 changes: 25 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 @@ -37,6 +37,8 @@ members = [
"farm-staking/metabonding-staking",
"farm-staking/metabonding-staking/meta",

"legacy-contracts/farm-staking-proxy-v1.3",
"legacy-contracts/farm-staking-proxy-v1.3/meta",
"legacy-contracts/simple-lock-legacy",
"legacy-contracts/simple-lock-legacy/meta",

Expand Down
43 changes: 43 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "farm-staking-legacy"
version = "0.0.0"
authors = [ "you",]
edition = "2018"
publish = false

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

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

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

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

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

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

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

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

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

[dev-dependencies.multiversx-sc-modules]
version = "=0.48.1"

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

[dev-dependencies]
num-bigint = "0.4.2"
85 changes: 85 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/docs/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Farm Staking setup steps

## Deployment

The init function takes the following arguments:
- farming_token_id - the farming token ID, which will also be the reward token
- division_safety_constant - used in some calculations for precision. I suggest something like 10^9.
- max_apr - a percentage of max APR, which will limit the user's rewards, with two decimals precision (i.e. 10_000 = 100%). Can be more than 100%.
- min_unbond_epochs - Number of epochs the user has to wait between unstake and unbond.
```
#[init]
fn init(
&self,
farming_token_id: TokenIdentifier,
division_safety_constant: BigUint,
max_apr: BigUint,
min_unbond_epochs: u64,
)
```

## Additional config

### Setup farm token

You have to register/issue the farm token and set its local roles, which is the token used for positions in the staking contract. This is done through the following endpoints:

```
#[payable("EGLD")]
#[endpoint(registerFarmToken)]
fn register_farm_token(
&self,
#[payment_amount] register_cost: BigUint,
token_display_name: ManagedBuffer,
token_ticker: ManagedBuffer,
num_decimals: usize,
)
```

For issue parameters format restrictions, take a look here: https://docs.multiversx.com/tokens/esdt-tokens/#parameters-format

payment_amount should be `0.05 EGLD`.

```
#[endpoint(setLocalRolesFarmToken)]
fn set_local_roles_farm_token(&self)
```

### Set per block rewards

```
#[endpoint(setPerBlockRewardAmount)]
fn set_per_block_rewards(&self, per_block_amount: BigUint)
```

Keep in mind amount has to take into consideration the token's decimals. So if you have a token with 18 decimals, you have to pass 10^18 for "1".

### Add the reward tokens

```
#[payable("*")]
#[endpoint(topUpRewards)]
fn top_up_rewards(
&self,
#[payment_token] payment_token: TokenIdentifier,
#[payment_amount] payment_amount: BigUint,
)
```

No args needed, you only need to pay the reward tokens. In the staking farm, rewards are not minted, but added by the owner.

### Final steps

First, you have to enable rewards generation:

```
#[endpoint(startProduceRewards)]
fn start_produce_rewards(&self)
```

Then, you have to the set the state to active:

```
#[endpoint]
fn resume(&self)
```
14 changes: 14 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "farm-staking-legacy-abi"

version = "0.0.0"
authors = [ "you",]
edition = "2018"
publish = false

[dependencies.farm-staking-legacy]
path = ".."

[dependencies.multiversx-sc-meta]
version = "0.48.1"
default-features = false
3 changes: 3 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/meta/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
multiversx_sc_meta::cli_main::<farm_staking::AbiProvider>();
}
3 changes: 3 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/multiversx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "rust"
}
186 changes: 186 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/src/custom_rewards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
multiversx_sc::imports!();
multiversx_sc::derive_imports!();

use common_structs::Nonce;
use config::MAX_PERCENT;

pub const BLOCKS_IN_YEAR: u64 = 31_536_000 / 6; // seconds_in_year / 6_seconds_per_block

#[multiversx_sc::module]
pub trait CustomRewardsModule:
config::ConfigModule + token_send::TokenSendModule + farm_token::FarmTokenModule
{
fn calculate_extra_rewards_since_last_allocation(&self) -> BigUint {
let current_block_nonce = self.blockchain().get_block_nonce();
let last_reward_nonce = self.last_reward_block_nonce().get();

if current_block_nonce > last_reward_nonce {
let extra_rewards_unbounded =
self.calculate_per_block_rewards(current_block_nonce, last_reward_nonce);

let farm_token_supply = self.farm_token_supply().get();
let extra_rewards_apr_bounded_per_block =
self.get_amount_apr_bounded(&farm_token_supply);

let block_nonce_diff = current_block_nonce - last_reward_nonce;
let extra_rewards_apr_bounded = extra_rewards_apr_bounded_per_block * block_nonce_diff;

self.last_reward_block_nonce().set(&current_block_nonce);

core::cmp::min(extra_rewards_unbounded, extra_rewards_apr_bounded)
} else {
BigUint::zero()
}
}

fn generate_aggregated_rewards(&self) {
let mut extra_rewards = self.calculate_extra_rewards_since_last_allocation();
if extra_rewards > 0 {
let mut accumulated_rewards = self.accumulated_rewards().get();
let total_rewards = &accumulated_rewards + &extra_rewards;
let reward_capacity = self.reward_capacity().get();
if total_rewards > reward_capacity {
let amount_over_capacity = total_rewards - reward_capacity;
extra_rewards -= amount_over_capacity;
}

accumulated_rewards += &extra_rewards;
self.accumulated_rewards().set(&accumulated_rewards);

self.update_reward_per_share(&extra_rewards);
}
}

#[only_owner]
#[payable("*")]
#[endpoint(topUpRewards)]
fn top_up_rewards(&self) {
let (payment_amount, payment_token) = self.call_value().payment_token_pair();
let reward_token_id = self.reward_token_id().get();
require!(payment_token == reward_token_id, "Invalid token");

self.reward_capacity().update(|r| *r += payment_amount);
}

#[only_owner]
#[endpoint]
fn end_produce_rewards(&self) {
self.generate_aggregated_rewards();
self.produce_rewards_enabled().set(&false);
}

#[only_owner]
#[endpoint(setPerBlockRewardAmount)]
fn set_per_block_rewards(&self, per_block_amount: BigUint) {
require!(per_block_amount != 0, "Amount cannot be zero");

self.generate_aggregated_rewards();
self.per_block_reward_amount().set(&per_block_amount);
}

#[only_owner]
#[endpoint(setMaxApr)]
fn set_max_apr(&self, max_apr: BigUint) {
require!(max_apr != 0, "Max APR cannot be zero");

self.max_annual_percentage_rewards().set(&max_apr);
}

#[only_owner]
#[endpoint(setMinUnbondEpochs)]
fn set_min_unbond_epochs(&self, min_unbond_epochs: u64) {
self.min_unbond_epochs().set(&min_unbond_epochs);
}

fn calculate_per_block_rewards(
&self,
current_block_nonce: Nonce,
last_reward_block_nonce: Nonce,
) -> BigUint {
if current_block_nonce <= last_reward_block_nonce || !self.produces_per_block_rewards() {
return BigUint::zero();
}

let per_block_reward = self.per_block_reward_amount().get();
let block_nonce_diff = current_block_nonce - last_reward_block_nonce;

per_block_reward * block_nonce_diff
}

fn update_reward_per_share(&self, reward_increase: &BigUint) {
let farm_token_supply = self.farm_token_supply().get();
if farm_token_supply > 0 {
let increase =
self.calculate_reward_per_share_increase(reward_increase, &farm_token_supply);
self.reward_per_share().update(|r| *r += increase);
}
}

fn calculate_reward_per_share_increase(
&self,
reward_increase: &BigUint,
farm_token_supply: &BigUint,
) -> BigUint {
&(reward_increase * &self.division_safety_constant().get()) / farm_token_supply
}

fn calculate_reward(
&self,
amount: &BigUint,
current_reward_per_share: &BigUint,
initial_reward_per_share: &BigUint,
) -> BigUint {
if current_reward_per_share > initial_reward_per_share {
let reward_per_share_diff = current_reward_per_share - initial_reward_per_share;
amount * &reward_per_share_diff / self.division_safety_constant().get()
} else {
BigUint::zero()
}
}

fn get_amount_apr_bounded(&self, amount: &BigUint) -> BigUint {
let max_apr = self.max_annual_percentage_rewards().get();
amount * &max_apr / MAX_PERCENT / BLOCKS_IN_YEAR
}

#[only_owner]
#[endpoint(startProduceRewards)]
fn start_produce_rewards(&self) {
require!(
self.per_block_reward_amount().get() != 0,
"Cannot produce zero reward amount"
);
require!(
!self.produce_rewards_enabled().get(),
"Producing rewards is already enabled"
);
let current_nonce = self.blockchain().get_block_nonce();
self.produce_rewards_enabled().set(&true);
self.last_reward_block_nonce().set(&current_nonce);
}

#[inline(always)]
fn produces_per_block_rewards(&self) -> bool {
self.produce_rewards_enabled().get()
}

#[view(getRewardPerShare)]
#[storage_mapper("reward_per_share")]
fn reward_per_share(&self) -> SingleValueMapper<BigUint>;

#[view(getAccumulatedRewards)]
#[storage_mapper("accumulatedRewards")]
fn accumulated_rewards(&self) -> SingleValueMapper<BigUint>;

#[view(getRewardCapacity)]
#[storage_mapper("reward_capacity")]
fn reward_capacity(&self) -> SingleValueMapper<BigUint>;

#[view(getAnnualPercentageRewards)]
#[storage_mapper("annualPercentageRewards")]
fn max_annual_percentage_rewards(&self) -> SingleValueMapper<BigUint>;

#[view(getMinUnbondEpochs)]
#[storage_mapper("minUnbondEpochs")]
fn min_unbond_epochs(&self) -> SingleValueMapper<u64>;
}
Loading
Loading