Skip to content

Commit

Permalink
Add legacy SC: farm staking proxy v1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
CostinCarabas committed Jun 14, 2024
1 parent c4d11a9 commit 163dfe6
Show file tree
Hide file tree
Showing 13 changed files with 1,869 additions and 0 deletions.
40 changes: 40 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,40 @@
[package]
name = "farm-staking"
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_old]
path = "../../common/modules/token_merge_old"

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

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

[dependencies.elrond-wasm]
version = "0.28.0"
features = ["cb_closure_managed_deser"]

[dev-dependencies.elrond-wasm-debug]
version = "0.28.0"

[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.elrond.com/developers/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)
```
3 changes: 3 additions & 0 deletions legacy-contracts/farm-staking-proxy-v1.3/elrond.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "rust"
}
16 changes: 16 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,16 @@
[package]
name = "farm-staking-abi"

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

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

[dependencies.elrond-wasm]
version = "0.28.0"

[dependencies.elrond-wasm-debug]
version = "0.28.0"
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() {
elrond_wasm_debug::meta::perform::<farm_staking::AbiProvider>();
}
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 @@
elrond_wasm::imports!();
elrond_wasm::derive_imports!();

use common_structs_old::Nonce;
use config::MAX_PERCENT;

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

#[elrond_wasm::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

0 comments on commit 163dfe6

Please sign in to comment.