0x1::YieldFarmingV2
Exp
Farming
FarmingAsset
Stake
ParameterModifyCapability
HarvestCapability
exp_direct
exp_direct_expand
mantissa
add_exp
exp
add_u128
sub_u128
mul_u128
div_u128
truncate
initialize
add_asset
modify_parameter
stake
stake_for_cap
unstake
unstake_with_cap
harvest
harvest_with_cap
query_gov_token_amount
query_total_stake
query_stake
query_info
calculate_harvest_index_with_asset
calculate_harvest_index_weight_zero
calculate_harvest_index
calculate_withdraw_amount
exists_at
exists_asset_at
exists_stake_at_address
use 0x1::Errors;
use 0x1::Math;
use 0x1::Signer;
use 0x1::Timestamp;
use 0x1::Token;
Exp
struct Exp has copy, drop, store
mantissa: u128
Farming
The object of yield farming RewardTokenT meaning token of yield farming
struct Farming<PoolType, RewardTokenT> has store, key
treasury_token: Token::Token<RewardTokenT>
FarmingAsset
struct FarmingAsset<PoolType, AssetT> has store, key
asset_total_weight: u128
harvest_index: u128
last_update_timestamp: u64
release_per_second: u128
start_time: u64
alive: bool
Stake
To store user’s asset token
struct Stake<PoolType, AssetT> has store, key
asset: AssetT
asset_weight: u128
last_harvest_index: u128
gain: u128
ParameterModifyCapability
Capability to modify parameter such as period and release amount
struct ParameterModifyCapability<PoolType, AssetT> has store, key
dummy_field: bool
HarvestCapability
Harvest ability to harvest
struct HarvestCapability<PoolType, AssetT> has store, key
trigger: address
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;
exp_direct
fun exp_direct(num: u128): YieldFarmingV2::Exp
fun exp_direct(num: u128): Exp {
Exp {
mantissa: num
}
}
exp_direct_expand
fun exp_direct_expand(num: u128): YieldFarmingV2::Exp
fun exp_direct_expand(num: u128): Exp {
Exp {
mantissa: mul_u128(num, EXP_SCALE)
}
}
mantissa
fun mantissa(a: YieldFarmingV2::Exp): u128
add_exp
fun add_exp(a: YieldFarmingV2::Exp, b: YieldFarmingV2::Exp): YieldFarmingV2::Exp
fun add_exp(a: Exp, b: Exp): Exp {
Exp {
mantissa: add_u128(a.mantissa, b.mantissa)
}
}
exp
fun exp(num: u128, denom: u128): YieldFarmingV2::Exp
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
}
}
add_u128
fun add_u128(a: u128, b: u128): u128
fun add_u128(a: u128, b: u128): u128 {
a + b
}
sub_u128
fun sub_u128(a: u128, b: u128): u128
fun sub_u128(a: u128, b: u128): u128 {
a - b
}
mul_u128
fun mul_u128(a: u128, b: u128): u128
fun mul_u128(a: u128, b: u128): u128 {
if (a == 0 || b == 0) {
return 0
};
a * b
}
div_u128
fun div_u128(a: u128, b: u128): u128
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
}
truncate
fun truncate(exp: YieldFarmingV2::Exp): u128
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>)
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,
});
}
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>
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> {}
}
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)
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;
}
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>)
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);
}
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>
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 }
}
unstake
Unstake asset from farming pool
public fun unstake<PoolType: store, RewardTokenT: store, AssetT: store>(signer: &signer, broker: address): (AssetT, Token::Token<RewardTokenT>)
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)
}
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>)
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)
}
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>
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)
}
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>
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
}
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
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
}
query_total_stake
Query total stake count from yield farming resource
public fun query_total_stake<PoolType: store, AssetT: store>(broker: address): u128
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
}
query_stake
Query stake weight from user staking objects.
public fun query_stake<PoolType: store, AssetT: store>(account: address): u128
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
}
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)
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
)
}
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
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
)
}
}
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
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))
}
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
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))
}
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
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))
}
exists_at
Check the Farming of TokenT is exists.
public fun exists_at<PoolType: store, RewardTokenT: store>(broker: address): bool
public fun exists_at<PoolType: store, RewardTokenT: store>(broker: address): bool {
exists<Farming<PoolType, RewardTokenT>>(broker)
}
exists_asset_at
Check the Farming of AsssetT is exists.
public fun exists_asset_at<PoolType: store, AssetT: store>(broker: address): bool
public fun exists_asset_at<PoolType: store, AssetT: store>(broker: address): bool {
exists<FarmingAsset<PoolType, AssetT>>(broker)
}
exists_stake_at_address
Check stake at address exists.
public fun exists_stake_at_address<PoolType: store, AssetT: store>(account: address): bool
public fun exists_stake_at_address<PoolType: store, AssetT: store>(account: address): bool {
exists<Stake<PoolType, AssetT>>(account)
}
pragma verify = false;