starcoin-framework

Module 0x1::YieldFarmingV2

use 0x1::Errors;
use 0x1::Math;
use 0x1::Signer;
use 0x1::Timestamp;
use 0x1::Token;

Struct Exp

struct Exp has copy, drop, store
Fields
mantissa: u128

Resource Farming

The object of yield farming RewardTokenT meaning token of yield farming

struct Farming<PoolType, RewardTokenT> has store, key
Fields
treasury_token: Token::Token<RewardTokenT>

Resource FarmingAsset

struct FarmingAsset<PoolType, AssetT> has store, key
Fields
asset_total_weight: u128
harvest_index: u128
last_update_timestamp: u64
release_per_second: u128
start_time: u64
alive: bool

Resource Stake

To store user’s asset token

struct Stake<PoolType, AssetT> has store, key
Fields
asset: AssetT
asset_weight: u128
last_harvest_index: u128
gain: u128

Resource ParameterModifyCapability

Capability to modify parameter such as period and release amount

struct ParameterModifyCapability<PoolType, AssetT> has store, key
Fields
dummy_field: bool

Resource HarvestCapability

Harvest ability to harvest

struct HarvestCapability<PoolType, AssetT> has store, key
Fields
trigger: address

Constants

const ERR_EXP_DIVIDE_BY_ZERO: u64 = 107;

const ERR_FARMING_BALANCE_EXCEEDED: u64 = 108;

const ERR_FARMING_HAVERST_NO_GAIN: u64 = 105;

const ERR_FARMING_INIT_REPEATE: u64 = 101;

const ERR_FARMING_NOT_ENOUGH_ASSET: u64 = 109;

const ERR_FARMING_NOT_STILL_FREEZE: u64 = 102;

const ERR_FARMING_STAKE_EXISTS: u64 = 103;

const ERR_FARMING_STAKE_NOT_EXISTS: u64 = 104;

const ERR_FARMING_TIMESTAMP_INVALID: u64 = 110;

const ERR_FARMING_TOTAL_WEIGHT_IS_ZERO: u64 = 106;

const EXP_SCALE: u128 = 1000000000000000000;

const ERR_FARMING_ALIVE_STATE_INVALID: u64 = 114;

const ERR_FARMING_CALC_LAST_IDX_BIGGER_THAN_NOW: u64 = 112;

const ERR_FARMING_NOT_ALIVE: u64 = 113;

const ERR_FARMING_TOKEN_SCALE_OVERFLOW: u64 = 111;

const EXP_MAX_SCALE: u128 = 9;

Function exp_direct

fun exp_direct(num: u128): YieldFarmingV2::Exp
Implementation
fun exp_direct(num: u128): Exp {
    Exp {
        mantissa: num
    }
}

Function exp_direct_expand

fun exp_direct_expand(num: u128): YieldFarmingV2::Exp
Implementation
fun exp_direct_expand(num: u128): Exp {
    Exp {
        mantissa: mul_u128(num, EXP_SCALE)
    }
}

Function mantissa

fun mantissa(a: YieldFarmingV2::Exp): u128
Implementation
fun mantissa(a: Exp): u128 {
    a.mantissa
}

Function add_exp

fun add_exp(a: YieldFarmingV2::Exp, b: YieldFarmingV2::Exp): YieldFarmingV2::Exp
Implementation
fun add_exp(a: Exp, b: Exp): Exp {
    Exp {
        mantissa: add_u128(a.mantissa, b.mantissa)
    }
}

Function exp

fun exp(num: u128, denom: u128): YieldFarmingV2::Exp
Implementation
fun exp(num: u128, denom: u128): Exp {
    // if overflow move will abort
    let scaledNumerator = mul_u128(num, EXP_SCALE);
    let rational = div_u128(scaledNumerator, denom);
    Exp {
        mantissa: rational
    }
}

Function add_u128

fun add_u128(a: u128, b: u128): u128
Implementation
fun add_u128(a: u128, b: u128): u128 {
    a + b
}

Function sub_u128

fun sub_u128(a: u128, b: u128): u128
Implementation
fun sub_u128(a: u128, b: u128): u128 {
    a - b
}

Function mul_u128

fun mul_u128(a: u128, b: u128): u128
Implementation
fun mul_u128(a: u128, b: u128): u128 {
    if (a == 0 || b == 0) {
        return 0
    };
    a * b
}

Function div_u128

fun div_u128(a: u128, b: u128): u128
Implementation
fun div_u128(a: u128, b: u128): u128 {
    if (b == 0) {
        abort Errors::invalid_argument(ERR_EXP_DIVIDE_BY_ZERO)
    };
    if (a == 0) {
        return 0
    };
    a / b
}

Function truncate

fun truncate(exp: YieldFarmingV2::Exp): u128
Implementation
fun truncate(exp: Exp): u128 {
    return exp.mantissa / EXP_SCALE
}

Function initialize

Called by token issuer this will declare a yield farming pool

public fun initialize<PoolType: store, RewardTokenT: store>(signer: &signer, treasury_token: Token::Token<RewardTokenT>)
Implementation
public fun initialize<
    PoolType: store,
    RewardTokenT: store>(signer: &signer, treasury_token: Token::Token<RewardTokenT>) {
    let scaling_factor = Math::pow(10, (EXP_MAX_SCALE as u64));
    let token_scale = Token::scaling_factor<RewardTokenT>();
    assert!(token_scale <= scaling_factor, Errors::limit_exceeded(ERR_FARMING_TOKEN_SCALE_OVERFLOW));
    assert!(!exists_at<PoolType, RewardTokenT>(
        Signer::address_of(signer)), Errors::invalid_state(ERR_FARMING_INIT_REPEATE));

    move_to(signer, Farming<PoolType, RewardTokenT> {
        treasury_token,
    });
}

Function add_asset

Add asset pools

public fun add_asset<PoolType: store, AssetT: store>(signer: &signer, release_per_second: u128, delay: u64): YieldFarmingV2::ParameterModifyCapability<PoolType, AssetT>
Implementation
public fun add_asset<PoolType: store, AssetT: store>(
    signer: &signer,
    release_per_second: u128,
    delay: u64): ParameterModifyCapability<PoolType, AssetT> {
    assert!(!exists_asset_at<PoolType, AssetT>(
        Signer::address_of(signer)),
        Errors::invalid_state(ERR_FARMING_INIT_REPEATE));

    let now_seconds = Timestamp::now_seconds();

    move_to(signer, FarmingAsset<PoolType, AssetT> {
        asset_total_weight: 0,
        harvest_index: 0,
        last_update_timestamp: now_seconds,
        release_per_second,
        start_time: now_seconds + delay,
        alive: true
    });
    ParameterModifyCapability<PoolType, AssetT> {}
}

Function modify_parameter

Remove asset for make this pool to the state of not alive Please make sure all user unstaking from this pool

public fun modify_parameter<PoolType: store, RewardTokenT: store, AssetT: store>(_cap: &YieldFarmingV2::ParameterModifyCapability<PoolType, AssetT>, broker: address, release_per_second: u128, alive: bool)
Implementation
public fun modify_parameter<PoolType: store, RewardTokenT: store, AssetT: store>(
    _cap: &ParameterModifyCapability<PoolType, AssetT>,
    broker: address,
    release_per_second: u128,
    alive: bool) acquires FarmingAsset {

    // Not support to shuttingdown alive state.
    assert!(alive, Errors::invalid_state(ERR_FARMING_ALIVE_STATE_INVALID));

    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    // assert!(farming_asset.alive != alive, Errors::invalid_state(ERR_FARMING_ALIVE_STATE_INVALID));

    let now_seconds = Timestamp::now_seconds();

    farming_asset.release_per_second = release_per_second;
    farming_asset.last_update_timestamp = now_seconds;

    // if the pool is alive, then update index
    if (farming_asset.alive) {
        farming_asset.harvest_index = calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset, now_seconds);
    };
    farming_asset.alive = alive;
}

Function stake

Call by stake user, staking amount of asset in order to get yield farming token

public fun stake<PoolType: store, RewardTokenT: store, AssetT: store>(signer: &signer, broker: address, asset: AssetT, asset_weight: u128, _cap: &YieldFarmingV2::ParameterModifyCapability<PoolType, AssetT>)
Implementation
public fun stake<PoolType: store, RewardTokenT: store, AssetT: store>(
    signer: &signer,
    broker: address,
    asset: AssetT,
    asset_weight: u128,
    _cap: &ParameterModifyCapability<PoolType, AssetT>) acquires FarmingAsset {
    let harvest_cap = stake_for_cap<
        PoolType,
        RewardTokenT,
        AssetT>(signer, broker, asset, asset_weight, _cap);

    move_to(signer, harvest_cap);
}

Function stake_for_cap

public fun stake_for_cap<PoolType: store, RewardTokenT: store, AssetT: store>(signer: &signer, broker: address, asset: AssetT, asset_weight: u128, _cap: &YieldFarmingV2::ParameterModifyCapability<PoolType, AssetT>): YieldFarmingV2::HarvestCapability<PoolType, AssetT>
Implementation
public fun stake_for_cap<PoolType: store, RewardTokenT: store, AssetT: store>(
    signer: &signer,
    broker: address,
    asset: AssetT,
    asset_weight: u128,
    _cap: &ParameterModifyCapability<PoolType, AssetT>)
: HarvestCapability<PoolType, AssetT> acquires FarmingAsset {
    let account = Signer::address_of(signer);
    assert!(!exists_stake_at_address<PoolType, AssetT>(account),
        Errors::invalid_state(ERR_FARMING_STAKE_EXISTS));

    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    assert!(farming_asset.alive, Errors::invalid_state(ERR_FARMING_NOT_ALIVE));

    // Check locking time
    let now_seconds = Timestamp::now_seconds();
    assert!(farming_asset.start_time <= now_seconds, Errors::invalid_state(ERR_FARMING_NOT_STILL_FREEZE));

    let time_period = now_seconds - farming_asset.last_update_timestamp;

    if (farming_asset.asset_total_weight <= 0) {
        // Stake as first user
        let gain = farming_asset.release_per_second * (time_period as u128);
        move_to(signer, Stake<PoolType, AssetT> {
            asset,
            asset_weight,
            last_harvest_index: 0,
            gain,
        });
        farming_asset.harvest_index = 0;
        farming_asset.asset_total_weight = asset_weight;
    } else {
        let new_harvest_index = calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset, now_seconds);
        move_to(signer, Stake<PoolType, AssetT> {
            asset,
            asset_weight,
            last_harvest_index: new_harvest_index,
            gain: 0,
        });
        farming_asset.asset_total_weight = farming_asset.asset_total_weight + asset_weight;
        farming_asset.harvest_index = new_harvest_index;
    };
    farming_asset.last_update_timestamp = now_seconds;
    HarvestCapability<PoolType, AssetT> { trigger: account }
}

Function unstake

Unstake asset from farming pool

public fun unstake<PoolType: store, RewardTokenT: store, AssetT: store>(signer: &signer, broker: address): (AssetT, Token::Token<RewardTokenT>)
Implementation
public fun unstake<PoolType: store, RewardTokenT: store, AssetT: store>(
    signer: &signer,
    broker: address)
: (AssetT, Token::Token<RewardTokenT>) acquires HarvestCapability, Farming, FarmingAsset, Stake {
    let account = Signer::address_of(signer);
    let cap = move_from<HarvestCapability<PoolType, AssetT>>(account);
    unstake_with_cap(broker, cap)
}

Function unstake_with_cap

public fun unstake_with_cap<PoolType: store, RewardTokenT: store, AssetT: store>(broker: address, cap: YieldFarmingV2::HarvestCapability<PoolType, AssetT>): (AssetT, Token::Token<RewardTokenT>)
Implementation
public fun unstake_with_cap<PoolType: store, RewardTokenT: store, AssetT: store>(
    broker: address,
    cap: HarvestCapability<PoolType, AssetT>)
: (AssetT, Token::Token<RewardTokenT>) acquires Farming, FarmingAsset, Stake {
    // Destroy capability
    let HarvestCapability<PoolType, AssetT> { trigger } = cap;

    let farming = borrow_global_mut<Farming<PoolType, RewardTokenT>>(broker);
    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);

    let Stake<PoolType, AssetT> { last_harvest_index, asset_weight, asset, gain } =
        move_from<Stake<PoolType, AssetT>>(trigger);

    let now_seconds = Timestamp::now_seconds();
    let new_harvest_index = calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset, now_seconds);

    let period_gain = calculate_withdraw_amount(new_harvest_index, last_harvest_index, asset_weight);
    let total_gain = gain + period_gain;
    let withdraw_token = Token::withdraw<RewardTokenT>(&mut farming.treasury_token, total_gain);

    // Dont update harvest index that because the `Stake` object has droped.
    // let new_index = calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset, now_seconds);
    assert!(farming_asset.asset_total_weight >= asset_weight, Errors::invalid_state(ERR_FARMING_NOT_ENOUGH_ASSET));

    // Update farm asset
    farming_asset.asset_total_weight = farming_asset.asset_total_weight - asset_weight;
    farming_asset.last_update_timestamp = now_seconds;

    if (farming_asset.alive) {
        farming_asset.harvest_index = new_harvest_index;
    };

    (asset, withdraw_token)
}

Function harvest

Harvest yield farming token from stake

public fun harvest<PoolType: store, RewardTokenT: store, AssetT: store>(signer: &signer, broker: address, amount: u128): Token::Token<RewardTokenT>
Implementation
public fun harvest<PoolType: store,
                   RewardTokenT: store,
                   AssetT: store>(
    signer: &signer,
    broker: address,
    amount: u128) : Token::Token<RewardTokenT> acquires HarvestCapability, Farming, FarmingAsset, Stake {
    let account = Signer::address_of(signer);
    let cap = borrow_global_mut<HarvestCapability<PoolType, AssetT>>(account);
    harvest_with_cap(broker, amount, cap)
}

Function harvest_with_cap

public fun harvest_with_cap<PoolType: store, RewardTokenT: store, AssetT: store>(broker: address, amount: u128, _cap: &YieldFarmingV2::HarvestCapability<PoolType, AssetT>): Token::Token<RewardTokenT>
Implementation
public fun harvest_with_cap<PoolType: store,
                            RewardTokenT: store,
                            AssetT: store>(
    broker: address,
    amount: u128,
    _cap: &HarvestCapability<PoolType, AssetT>): Token::Token<RewardTokenT> acquires Farming, FarmingAsset, Stake {
    let farming = borrow_global_mut<Farming<PoolType, RewardTokenT>>(broker);
    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    let stake = borrow_global_mut<Stake<PoolType, AssetT>>(_cap.trigger);

    let now_seconds = Timestamp::now_seconds();
    let new_harvest_index = calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset, now_seconds);

    let period_gain = calculate_withdraw_amount(
        new_harvest_index,
        stake.last_harvest_index,
        stake.asset_weight
    );

    let total_gain = stake.gain + period_gain;
    //assert!(total_gain > 0, Errors::limit_exceeded(ERR_FARMING_HAVERST_NO_GAIN));
    assert!(total_gain >= amount, Errors::limit_exceeded(ERR_FARMING_BALANCE_EXCEEDED));

    let withdraw_amount = if (amount <= 0) {
        total_gain
    } else {
        amount
    };

    let withdraw_token = Token::withdraw<RewardTokenT>(&mut farming.treasury_token, withdraw_amount);
    stake.gain = total_gain - withdraw_amount;
    stake.last_harvest_index = new_harvest_index;

    if (farming_asset.alive) {
        farming_asset.harvest_index = new_harvest_index;
    };
    farming_asset.last_update_timestamp = now_seconds;

    withdraw_token
}

Function query_gov_token_amount

The user can quering all yield farming amount in any time and scene

public fun query_gov_token_amount<PoolType: store, RewardTokenT: store, AssetT: store>(account: address, broker: address): u128
Implementation
public fun query_gov_token_amount<PoolType: store,
                                  RewardTokenT: store,
                                  AssetT: store>(account: address, broker: address): u128 acquires FarmingAsset, Stake {
    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    let stake = borrow_global_mut<Stake<PoolType, AssetT>>(account);
    let now_seconds = Timestamp::now_seconds();

    let new_harvest_index = calculate_harvest_index_with_asset<PoolType, AssetT>(
        farming_asset,
        now_seconds
    );

    let new_gain = calculate_withdraw_amount(
        new_harvest_index,
        stake.last_harvest_index,
        stake.asset_weight
    );
    stake.gain + new_gain
}

Function query_total_stake

Query total stake count from yield farming resource

public fun query_total_stake<PoolType: store, AssetT: store>(broker: address): u128
Implementation
public fun query_total_stake<PoolType: store,
                             AssetT: store>(broker: address): u128 acquires FarmingAsset {
    let farming_asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    farming_asset.asset_total_weight
}

Function query_stake

Query stake weight from user staking objects.

public fun query_stake<PoolType: store, AssetT: store>(account: address): u128
Implementation
public fun query_stake<PoolType: store,
                       AssetT: store>(account: address): u128 acquires Stake {
    let stake = borrow_global_mut<Stake<PoolType, AssetT>>(account);
    stake.asset_weight
}

Function query_info

Queyry pool info from pool type return value: (alive, release_per_second, asset_total_weight, harvest_index)

public fun query_info<PoolType: store, AssetT: store>(broker: address): (bool, u128, u128, u128)
Implementation
public fun query_info<PoolType: store, AssetT: store>(broker: address): (bool, u128, u128, u128) acquires FarmingAsset {
    let asset = borrow_global_mut<FarmingAsset<PoolType, AssetT>>(broker);
    (
        asset.alive,
        asset.release_per_second,
        asset.asset_total_weight,
        asset.harvest_index
    )
}

Function calculate_harvest_index_with_asset

Update farming asset

fun calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset: &YieldFarmingV2::FarmingAsset<PoolType, AssetT>, now_seconds: u64): u128
Implementation
fun calculate_harvest_index_with_asset<PoolType, AssetT>(farming_asset: &FarmingAsset<PoolType, AssetT>, now_seconds: u64): u128 {
    // Recalculate harvest index
    if (farming_asset.asset_total_weight <= 0) {
        calculate_harvest_index_weight_zero(
            farming_asset.harvest_index,
            farming_asset.last_update_timestamp,
            now_seconds,
            farming_asset.release_per_second
        )
    } else {
        calculate_harvest_index(
            farming_asset.harvest_index,
            farming_asset.asset_total_weight,
            farming_asset.last_update_timestamp,
            now_seconds,
            farming_asset.release_per_second
        )
    }
}

Function calculate_harvest_index_weight_zero

There is calculating from harvest index and global parameters without asset_total_weight

public fun calculate_harvest_index_weight_zero(harvest_index: u128, last_update_timestamp: u64, now_seconds: u64, release_per_second: u128): u128
Implementation
public fun calculate_harvest_index_weight_zero(harvest_index: u128,
                                               last_update_timestamp: u64,
                                               now_seconds: u64,
                                               release_per_second: u128): u128 {
    assert!(last_update_timestamp <= now_seconds, Errors::invalid_argument(ERR_FARMING_TIMESTAMP_INVALID));
    let time_period = now_seconds - last_update_timestamp;
    let addtion_index = release_per_second * ((time_period as u128));
    harvest_index + mantissa(exp_direct_expand(addtion_index))
}

Function calculate_harvest_index

There is calculating from harvest index and global parameters

public fun calculate_harvest_index(harvest_index: u128, asset_total_weight: u128, last_update_timestamp: u64, now_seconds: u64, release_per_second: u128): u128
Implementation
public fun calculate_harvest_index(harvest_index: u128,
                                   asset_total_weight: u128,
                                   last_update_timestamp: u64,
                                   now_seconds: u64,
                                   release_per_second: u128): u128 {
    assert!(asset_total_weight > 0, Errors::invalid_argument(ERR_FARMING_TOTAL_WEIGHT_IS_ZERO));
    assert!(last_update_timestamp <= now_seconds, Errors::invalid_argument(ERR_FARMING_TIMESTAMP_INVALID));

    let time_period = now_seconds - last_update_timestamp;
    let numr = (release_per_second * (time_period as u128));
    let denom = asset_total_weight;
    harvest_index + mantissa(exp(numr, denom))
}

Function calculate_withdraw_amount

This function will return a gain index

public fun calculate_withdraw_amount(harvest_index: u128, last_harvest_index: u128, asset_weight: u128): u128
Implementation
public fun calculate_withdraw_amount(harvest_index: u128,
                                     last_harvest_index: u128,
                                     asset_weight: u128): u128 {
    assert!(harvest_index >= last_harvest_index, Errors::invalid_argument(ERR_FARMING_CALC_LAST_IDX_BIGGER_THAN_NOW));
    let amount = asset_weight * (harvest_index - last_harvest_index);
    truncate(exp_direct(amount))
}

Function exists_at

Check the Farming of TokenT is exists.

public fun exists_at<PoolType: store, RewardTokenT: store>(broker: address): bool
Implementation
public fun exists_at<PoolType: store, RewardTokenT: store>(broker: address): bool {
    exists<Farming<PoolType, RewardTokenT>>(broker)
}

Function exists_asset_at

Check the Farming of AsssetT is exists.

public fun exists_asset_at<PoolType: store, AssetT: store>(broker: address): bool
Implementation
public fun exists_asset_at<PoolType: store, AssetT: store>(broker: address): bool {
    exists<FarmingAsset<PoolType, AssetT>>(broker)
}

Function exists_stake_at_address

Check stake at address exists.

public fun exists_stake_at_address<PoolType: store, AssetT: store>(account: address): bool
Implementation
public fun exists_stake_at_address<PoolType: store, AssetT: store>(account: address): bool {
    exists<Stake<PoolType, AssetT>>(account)
}

Module Specification

pragma verify = false;