diff --git a/aptos-move/framework/supra-framework/Move.toml b/aptos-move/framework/supra-framework/Move.toml index cf6daf68a323e..d469bc3e70c88 100644 --- a/aptos-move/framework/supra-framework/Move.toml +++ b/aptos-move/framework/supra-framework/Move.toml @@ -14,3 +14,4 @@ vm_reserved = "0x0" [dependencies] AptosStdlib = { local = "../aptos-stdlib" } MoveStdlib = { local = "../move-stdlib" } +core = { git = "https://github.com/Entropy-Foundation/dora-interface", subdir = "supra/testnet/core", rev = "dev" } diff --git a/aptos-move/framework/supra-framework/sources/configs/poel_config.move b/aptos-move/framework/supra-framework/sources/configs/poel_config.move new file mode 100644 index 0000000000000..bd978cd1a3b54 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/configs/poel_config.move @@ -0,0 +1 @@ +/// configs for poel \ No newline at end of file diff --git a/aptos-move/framework/supra-framework/sources/poel/iAsset.move b/aptos-move/framework/supra-framework/sources/poel/iAsset.move new file mode 100644 index 0000000000000..17bcece53bf36 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/iAsset.move @@ -0,0 +1,971 @@ +/// +/// The iAsset module provides a standardized token (iAsset) for users who deposit assets into the Intralayer vault. +/// Effectively, iAsset are coupons representing their users share in the Intralayer vaults for a specific asset. +/// Owning iAsset entitles the holder to earn staking rewards, which are paid in $Supra. +/// Holders of iAsset earn $Supra rewards from staking pools and can also use iAsset as collateral or as an underlying asset +/// for various DeFi utilities. Users can manage their iAssets freely—transferring them to other addresses or deploying them in DeFi pools. +/// Please note that transferring iAsset to a different address will transfer the accrual of rewards to the receiving address, +/// effective from the period following the transfer. + +module supra_framework::iAsset { + use aptos_std::simple_map::{Self, SimpleMap}; + use aptos_std::vector; + use aptos_std::smart_table::{Self, SmartTable}; + use std::signer; + use std::string; + use aptos_std::error; + use aptos_std::object::{Self, Object, ObjectCore}; + use aptos_std::math64; + use std::option::{Self, Option}; + use aptos_std::primary_fungible_store::Self; + use aptos_std::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata}; + use supra_framework::reward_distribution::update_rewards; + use supra_oracle::supra_oracle_storage; + use supra_framework::system_addresses::is_reserved_address; + + friend supra_framework::poel; + + /// The storage object name from object::create_named_object(address, OBJECT_NAME) + const IASSET_GLOBAL: vector = b"IASSET_GLOBAL"; + + /// thrown when the calling address is not poel as expected + const ENOT_FRAMEWORK_ADDRESS: u64 = 1; + + /// thrown when the the asset getting deployed is already deployed + const EIASSET_ALREADY_DELOYED: u64 = 2; + + /// thrown when the preminiting_OLC_index > current total liquidity cycle index + const EPREMINTING_OLC_EXCESS: u64 = 3; + + /// thrown when the asset balance is not enogh to perform an operation + const EBALANCE_NOT_ENOUGH: u64 = 4; + + /// thrown when the unlock_olc index is > current_cycle_index + const EUNLOCK_OLC_INDEX: u64 = 5; + + /// thrown when the redeem amount is lessthatn 0 + const EREDEEM_AMOUNT: u64 = 6; + + ///thrown when the wrong asset weight gets calculated + const EWRONG_WEIGHT: u64 = 7; + + ///thrown when the length of the collaterixation weight vector does not match table entries + const EWRONG_CWV_LENGTH: u64 = 8; + + ///thrown when the allowed signer is not the caller (owner) + const ENOT_OWNER: u64 = 9; + + ///thrown when the length of the desirability_score_vector does not match table entries + const EWRONG_DESIRABILITY_SCORE_LEN: u64 = 10; + + ///thrown when the asset id is not present in the TotalLiquidityTable + const EASSET_NOT_PRESENT: u64 = 11; + + /// thrown when the assets' iterations don't match their number + const EWRONG_ASSET_COUNT: u64 = 12; + + + const MAX_U64: u64 = 18446744073709551615; + + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + /// AssetEntry struct that holds the iAsset's user's metrics + struct AssetEntry has key, store, copy { + ///UserRewardIndex: Track RewardIndex specific to the user for each asset. + user_reward_index: u64, + ///Preminted_iAssets: Tracks number of iAssets that are preminted. + preminted_iAssets: u64, + ///Redeem_Requested_iAssets: Number of iAssets for which redemption has been requested. + redeem_requested_iAssets: u64, + ///Preminiting_OLC_Index: Records the index of the last cycle during which a preminting request was submitted for an asset. + preminiting_OLC_index: u64, + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + /// tracks the liquidity of every asset. + struct LiquidityTableItems has key, store { + pair_id: u32, + ///asset_supply: Total amount of the asset that has been bridged to create the iAsset. + asset_supply: u64, + ///desired_weights: Weights reflecting the asset's strategic importance. + desired_weight: u64, + ///desirability_score: Attractiveness score of the asset. + desirability_score: u64, + total_borrow_requests: u64, + total_withdraw_requests: u64, + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + /// Liquidity provider struct that holds the overal info on the user's iAssets + struct LiquidityProvider has key { + ///allocated_Rewards: Tracks the total rewards that are allocable to the user. + allocated_rewards: u64,//not updated at any point?? + ///unlock_OLC_Index: Registers the index of the lockup cycle when the user last submitted an unlock request. + unlock_olc_index: u64//not updated at any point?? + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + ///This struct aggregates comprehensive liquidity metrics and operational indices for assets managed within the system. + struct TotalLiquidity has key { + ///Index of the last observable lockup cycle (OLC), crucial for the minting of preminted tokens and the withdrawal of unlocked tokens. + current_olc_index: u64, //not update at any point?? + ///Represents the nominal value of all assets submitted to the system, indicating the total economic stake. + total_nominal_liquidity: u64 + } + + #[resource_group_member(group = aptos_std::object::ObjectGroup)] + /// This struct holds the refences for managing the iAsset fungible assets + struct ManagingRefs has key { + /// enables minting + mint_ref: MintRef, + /// enables transfers + transfer_ref: TransferRef, + /// enables burning + burn_ref: BurnRef, + } + + #[resource_group_member(group = aptos_std::object::ObjectGroup)] + /// this object tracks all iAssets created in this module + /// it stores assets in a table mapping their symbols to a bool + /// it can change to an asset id or an address depending on which is convenient + struct AssetTracker has key { + liquidity_provider_objects: SimpleMap, + asset_entry_tracker: SimpleMap, + assets: SmartTable, bool>,//maps asset symbol to a bool + } + + fun init_module(account: &signer) { + let constructor_ref = object::create_named_object(account, IASSET_GLOBAL); + + let global_signer = &object::generate_signer(&constructor_ref); + + move_to( + global_signer, + AssetTracker { + liquidity_provider_objects: simple_map::create(), + asset_entry_tracker: simple_map::create(), + assets: smart_table::new() + } + ); + + move_to(global_signer, TotalLiquidity { + current_olc_index: 0, + total_nominal_liquidity: 0, + }); + } + + /// Function create_new_iAsset + /// Description creates a new iAsset as a fungible asset and tracks it in the AssetTracker + /// @param iAsset_name: the name of the iAsset + /// @param iAsset_symbol: its corresponding symbol + public fun create_new_iAsset(supra_framework: &signer, iAsset_name: vector, iAsset_symbol: vector, pair_id: u32) acquires AssetTracker { + assert!(is_reserved_address(signer::address_of(account)), error::permission_denied(ENOT_FRAMEWORK_ADDRESS)); + let tracker_address = object::create_object_address(&@supra_framework, IASSET_GLOBAL); + + //assert that the iAsset has not been deployed yet + assert!(! + *smart_table::borrow(&borrow_global(tracker_address).assets, iAsset_symbol), + error::already_exists(EIASSET_ALREADY_DELOYED) + ); + + //Create the asste Metadata constructor_reference + let metadata_constructor_ref = &object::create_named_object( + account, iAsset_symbol + ); + + // Create a store enabled fungible asset + primary_fungible_store::create_primary_store_enabled_fungible_asset( + metadata_constructor_ref, + option::none(), + string::utf8(iAsset_name), + string::utf8(iAsset_symbol), + 6, + string::utf8(b""), + string::utf8(b""), + ); + + + // generate the mint, burn and transfer refs then store + let mint_ref = fungible_asset::generate_mint_ref(metadata_constructor_ref); + let transfer_ref = fungible_asset::generate_transfer_ref(metadata_constructor_ref); + let burn_ref = fungible_asset::generate_burn_ref(metadata_constructor_ref); + let metadata_object_signer = &object::generate_signer(metadata_constructor_ref); + move_to( + metadata_object_signer, + ManagingRefs { mint_ref, transfer_ref, burn_ref } + ); + + move_to(metadata_object_signer, LiquidityTableItems { + pair_id, + asset_supply: 0, + desired_weight: 0, + desirability_score: 0, + total_borrow_requests: 0, + total_withdraw_requests: 0, + }); + + // track the deployed asset in the AssetTracker assets table + smart_table::add( + &mut borrow_global_mut(tracker_address).assets, + iAsset_symbol, + true + ); + } + + #[view] + /// Function: asset_addess + /// Description: returns the address of the assets's metadata derived from the creator and its symbol + /// @param: symbol: the symbol used during the creation of the asset's object + public fun asset_address(symbol: vector): address { + object::create_object_address(&@supra_framework, symbol) + } + + + #[view] + /// Function: asset_metadata + /// Description: returns the assets's metadata derived from its address + /// @param symbol: the symbol used during the creation of the asset's object + public fun asset_metadata(symbol: vector): Object { + object::address_to_object(asset_address(symbol)) + } + + #[view] + /// Get the address of the global storage object + public fun get_storage_address(): address { + object::create_object_address(&@supra_framework, IASSET_GLOBAL) + } + + #[view] + /// Function get_liquidity_table_items(symbol) + /// Desctiption: Retrives the data in the liquidity table struct + /// @param: symbol - vector + /// returns: (asset_supply, iAsset_supply, desired_weight, desirability_score) + public fun get_liquidity_table_items(symbol: vector): (u32, u64, u64, u64,) acquires LiquidityTableItems { + let items = borrow_global(asset_address(symbol)); + + (items.pair_id, items.asset_supply, items.desired_weight, items.desirability_score) + } + + #[view] + /// Function get_total_liquidity + /// Desctiption: Retrives the data in the total_liquidity + /// returns: (current_olc_index, total_nominal_liquidity) + public fun get_total_liquidity(): (u64, u64) acquires TotalLiquidity { + let obj_address = object::create_object_address(&@supra_framework, IASSET_GLOBAL); + + let total_obj = borrow_global(obj_address); + + (total_obj.current_olc_index, total_obj.total_nominal_liquidity) + } + + #[view] + /// Function: get_asset_price(asset_symbol) + /// Description: Gets an asset's prce from the price oracle + /// @param: asset_symbol - vector + public fun get_asset_price(asset_symbol: vector): (u64, u16, u64, u64) acquires LiquidityTableItems { + let (pair_id, _, _, _) = get_liquidity_table_items(asset_symbol); + + let (value, decimal, timestamp, round) = supra_oracle_storage::get_price(pair_id); + let value_u64 = safe_u128_to_u64(value); + assert!(option::is_some(&value_u64), 12); + (0, decimal, timestamp, round) + } + + + fun safe_u128_to_u64(value: u128): Option { + if (value <= (MAX_U64 as u128)) { + option::some((value as u64)) + } else { + option::none() + } + } + + + #[view] + /// Function: get_iAsset_supply(asset: Object) + /// Description: Gets an asset's prce from the price oracle + /// @param: asset - Object + /// returns: u64 + public fun get_iAsset_supply(asset: Object): u64 { + (option::extract(&mut fungible_asset::supply(asset)) as u64) + + } + + + #[view] + /// Function: get_assets + /// Description: Gets all asset names from AssetTracker + /// returns: vector + public fun get_assets(): vector> acquires AssetTracker { + let tracked_assets = borrow_global(get_storage_address()); + + let return_value: vector> = vector::empty(); + + smart_table::for_each_ref, bool>( + &tracked_assets.assets, + | key, _value| + { + //let is_there: &bool = value; + let symbol = *key; + vector::push_back>(&mut return_value, symbol); + + }); + return_value + } + + + #[view] + /// Function: get_provider_address(account) + /// Description: Gets Liquidity provider object address for the account + /// @param: account - address of the user + /// returns: address + fun get_provider_address(account: address): address acquires AssetTracker { + let tracked_assets = borrow_global(get_storage_address()); + + *simple_map::borrow(&tracked_assets.liquidity_provider_objects, &account) + } + + + /// Function: create_iAsset_entry(account, asset) + /// Description: This function created an AssetEntry record on an address's FungibleStore's address + /// @param: account - address of the user + /// @param: asset - the target asset + public fun create_iAsset_entry(account: address, asset: Object) acquires AssetTracker { + //check if the liquidity provider object is initialized for the address + initialize_LiquidityProvider(account); + //get the primary store + let primary_store = primary_fungible_store::ensure_primary_store_exists(account, asset); + let store_address = object::object_address(&primary_store); + let tracked_assets = borrow_global_mut(get_storage_address()); + if (simple_map::borrow(&tracked_assets.asset_entry_tracker, &store_address) == &@0x0) + { + // create object + let constructor_ref = &object::create_object(store_address); + + let object_signer = &object::generate_signer(constructor_ref); + + move_to( + object_signer, + AssetEntry { + user_reward_index: 0, + preminted_iAssets: 0, + redeem_requested_iAssets: 0, + preminiting_OLC_index: 0, + + } + ); + + let object_address = object::address_from_constructor_ref(constructor_ref); + + simple_map::add(&mut tracked_assets.asset_entry_tracker, store_address, object_address); + } + } + + + ///Function to set up initial LiquidityProvider structures for asset management. + /// Desctription: Create an liquidityProvider for an addres if its not created + /// @param: user_address - address of the user + public fun initialize_LiquidityProvider(user_address: address) acquires AssetTracker { + + // check if the object was created and tracked + let liquidity_provider_address = get_provider_address(user_address); + if (!object::object_exists(liquidity_provider_address)) { + let constructor_ref = object::create_object(user_address); + let account = &object::generate_signer(&constructor_ref); + let liquidity_provider = LiquidityProvider { + allocated_rewards: 0u64, + unlock_olc_index: 0u64 + }; + move_to(account, liquidity_provider); + } + } + + + #[view] + /// Function: get_asset_entry + /// returns the iAsset entry values as a tuple + /// @param: user_address - the user address geting checked + /// @param: asset - the asset metadata + /// @return (u64, u64, u64, u64) - (user_reward_index, preminted_iAssets, redeem_requested_iAssets, preminiting_OLC_index) + public fun get_asset_entry(user_address: address, asset: Object): (u64, u64, u64, u64) acquires AssetEntry { + let store_address = primary_fungible_store::primary_store_address(user_address, asset); + + if (fungible_asset::store_exists(store_address)) { + let item = borrow_global_mut(store_address); + (item.user_reward_index, item.preminted_iAssets, item.redeem_requested_iAssets, item.preminiting_OLC_index) + + } else { + (0, 0, 0, 0) + } + } + + + /// Function: premint_iAsset + /// Description: is applied to mint iAssets as soon as some amount of the original asset has been submitted to the interalayer vaults. + /// @notice: premint function can be called only by PoEL contract + /// @param: asset_amount asssets to be minted + /// @param: asset: the asset getting minted + /// @param: receiver the receiving address + public(friend) fun premint_iAsset( + asset_amount: u64, + asset_symbol: vector, + receiver: address, + asset_supply: u64, + ) acquires AssetEntry, ManagingRefs, TotalLiquidity, AssetTracker { + + //ensure the reciver has a fungible store of the iAsset + let primary_store = primary_fungible_store::ensure_primary_store_exists(receiver, asset_metadata(asset_symbol)); + // check whether the AssetEntry struct is in the store address + create_iAsset_entry(receiver, asset_metadata(asset_symbol)); + + mint_iAsset(receiver, asset_symbol); + + let total_liquidity_ref = borrow_global(get_storage_address()); + + let asset_entry = borrow_global_mut(object::object_address(&primary_store)); + let iAsset_amount = previewMint(asset_supply, asset_amount, asset_metadata(asset_symbol)); + + asset_entry.preminted_iAssets = (asset_entry.preminted_iAssets + iAsset_amount); + asset_entry.preminiting_OLC_index = total_liquidity_ref.current_olc_index; + } + + + /// Function: mint_iAsset + /// Purpose: Intended to mint the pre-minted tokens of iAssets. + /// The function can be called by anyone, not necessarily by the user themselves. + /// @param: user_address - the user address geting checked + /// @param: asset - the asset metadata + public fun mint_iAsset( + user_address: address, + asset_symbol: vector, + ) acquires ManagingRefs, AssetEntry, TotalLiquidity, AssetTracker { + + let mint_ref = &borrow_global( + asset_address(asset_symbol) + ).mint_ref; + + let total_liquidity_ref = borrow_global(get_storage_address()); + + let primary_store = primary_fungible_store::ensure_primary_store_exists(user_address, asset_metadata(asset_symbol)); + + //assert!(exists(object::object_address(&primary_store))); + create_iAsset_entry(user_address, asset_metadata(asset_symbol)); + + let asset_entry = borrow_global_mut(object::object_address(&primary_store)); + + //Assert that Preminiting_OLC_Index < current_cycle_index. + assert!( + asset_entry.preminiting_OLC_index < total_liquidity_ref.current_olc_index, + error::invalid_state(EPREMINTING_OLC_EXCESS) + ); + //Check PremintedAssetBalance (fetched from iAsset_table using assetID) . If PremintedAssetBalance > 0: + if (asset_entry.preminted_iAssets > 0) { + + //mint into primary store + primary_fungible_store::mint(mint_ref, user_address, asset_entry.preminted_iAssets); + //Set PremintedAssetBalance = 0. + asset_entry.preminted_iAssets = 0; + //Call update_rewards(owner address, asset). + update_rewards(user_address, asset_entry.user_reward_index, asset_metadata(asset_symbol)); + } + } + + #[view] + /// previewMint(asset_amount, assetID): + /// Purpose: Calculates the total amount of iAsset that needs to be minted based on the submitted original assets. + /// @param: amount - the amount to mint + /// @param: asset - the asset metadata + public fun previewMint(asset_supply: u64, asset_amount: u64, asset: Object): u64 { + + // get the iAsset total supply + let iAsset_supply = get_iAsset_supply(asset); + + //Calculate iasset_amount based on the current iAsset_supply: + let iasset_amount: u64; + + //If iAsset_supply equals 0, set iasset_amount to asset_amount. + if (iAsset_supply == 0) { + iasset_amount = asset_amount; + } + // If iAsset_supply is greater than 0, calculate iasset_amount as the rounded up result of + // (asset_amount * asset_supply / iAsset_supply) to account for division truncation. + else { + + // rounding up when division truncation occurs + iasset_amount = (asset_amount * iAsset_supply + asset_supply - 1) / asset_supply + 1; + }; + //Return iasset_amount + iasset_amount + } + + /// Function: previewRedeem(iAsset_amount, asset): preview the amount of assets to receive by specifying the amount of iAssets to redeem + /// @param: iAsset_amount - the iAsset amount getting previewed + /// @param: asset - the asset metadata + /// return u64 - from convertToAssets(iAsset_amount, asset) + public fun previewRedeem(asset_supply: u64, iAsset_amount: u64, asset: Object): u64 { + let asset_amount = convertToAssets(asset_supply, iAsset_amount, asset); + asset_amount + } + + /// previewWithdraw(asset_amount, asset): preview the amount of iAsset to burn by specifying the amount of assets that would be withdrawn + /// @param: iAsset_amount - the iAsset amount getting previewed + /// @param: asset - the asset metadata + /// return: u64 - calculated iAsset_to_burn + public fun previewWithdraw(asset_supply: u64, asset_amount: u64, asset: Object): u64 { + //let (_, asset_supply, iAsset_supply, _, _) = get_liquidity_table_items(asset); + + let iAsset_supply = get_iAsset_supply(asset); + + let iAsset_to_burn: u64; + + if (iAsset_supply == 0) { + iAsset_to_burn = asset_amount; + } else { + //@phydy: to change + let totalAssets = asset_supply;//getTotalAssets(asset_id); // Assume this function fetches total assets + iAsset_to_burn = (asset_amount * iAsset_supply + totalAssets - 1) / totalAssets + 1; + }; + + // Return the calculated iAsset_to_burn + iAsset_to_burn + } + + /// Function: redeem_request(account, iAsset_amount, asset, receiver_address) + /// Description: A request to redeem iAssets + /// @param: account - the account submiting the redeem request + /// @param: iAsset_amount - amount of iAssets getting redeemed + /// @param: asset - iAsset getting redeemed + /// @param: receiver_address - address receiving the assets on the intra laver vault + public fun redeem_request( + account: &signer, + iAsset_amount: u64, + asset_symbol: vector, + receiver_address: address, + current_cycle_index: u64 + ) acquires AssetEntry, ManagingRefs, LiquidityTableItems, AssetTracker, LiquidityProvider { + let asset = asset_metadata(asset_symbol); + let account_address = signer::address_of(account); + let iAssetBalance = primary_fungible_store::balance(account_address, asset); + + assert!(iAssetBalance >= iAsset_amount, error::invalid_state(EBALANCE_NOT_ENOUGH)); + + let primary_store = primary_fungible_store::ensure_primary_store_exists(account_address, asset); + + redeem_iAsset(account, asset, receiver_address, current_cycle_index, iAsset_amount); + + let asset_entry = borrow_global_mut(object::object_address(&primary_store)); + + //burn the assets + let burn_ref = &borrow_global(object::object_address(&asset)).burn_ref; + + primary_fungible_store::burn(burn_ref, account_address, iAsset_amount); + + asset_entry.redeem_requested_iAssets = asset_entry.redeem_requested_iAssets + iAsset_amount; + + update_rewards(account_address, asset_entry.user_reward_index, asset); + + let current_cycle_index = current_cycle_index; + + let provider_ref = borrow_global_mut(get_provider_address(account_address)); + + provider_ref.unlock_olc_index = current_cycle_index; + reduce_asset_supply(asset_symbol, iAsset_amount); + + } + + /// Function: redeem_iAsset(account, asset, receiver_address) + /// Description: called to to redeem iAssets ie withdraw + /// @param: account - the account submiting the redeem request + /// @param: asset - iAsset getting redeemed + /// @param: receiver_address - address receiving the assets on the intra laver vault + fun redeem_iAsset( + account: &signer, + asset: Object, + _receiver_address: address, + current_cycle_index: u64, + asset_supply: u64 + ) acquires LiquidityProvider, AssetEntry, AssetTracker { + let account_address = signer::address_of(account); + + let primary_store = primary_fungible_store::ensure_primary_store_exists(account_address, asset); + + create_iAsset_entry(account_address, asset); + let provider_ref = borrow_global_mut(get_provider_address(account_address)); + + + let asset_entry = borrow_global_mut(object::object_address(&primary_store)); + + // Ensure that the unlock_olc_index is less than the current cycle index + assert!(provider_ref.unlock_olc_index < current_cycle_index, error::invalid_state(EUNLOCK_OLC_INDEX)); + + // Ensure that redeem_requested_iAssets is greater than or equal to 0 + assert!(asset_entry.redeem_requested_iAssets >= 0, error::invalid_state(EREDEEM_AMOUNT)); + + let _asset_to_withdraw = previewWithdraw(asset_supply, asset_entry.redeem_requested_iAssets, asset); + + // Verify that the receiver address is valid (assuming a helper function verify_receiver_address exists) + //assert!(verify_receiver_address(receiver_address), 2); + + asset_entry.redeem_requested_iAssets = 0; + + + + //Bridge::trigger_sendToUser(asset_id, asset_to_withdraw, receiver_address); +// + //IntraLayerVault::sendToUser(asset_id, asset_to_withdraw, receiver_address); + } + + /// Function: transfer_iAsset(account, asset, receiver_address) + /// Description: called to transfer iAssets to another address + /// @param: account - the account submiting the redeem request + /// @param: asset - iAsset getting redeemed + /// @param: receiver_address - address receiving the assets + public fun transfer_iAsset( + account: &signer, + iAsset_amount: u64, + asset: Object, + receiver_address: address, + ) acquires AssetEntry, AssetTracker { + let owner_address = signer::address_of(account); + let owner_primary_store = primary_fungible_store::ensure_primary_store_exists(owner_address, asset); + //let receiver_primary_store = primary_fungible_store::ensure_primary_store_exists(receiver_address, asset); + + let owner_asset_entry = borrow_global_mut(object::object_address(&owner_primary_store)); + + // Ensure the asset is initialized in the owner's LiquidityProvider struct + //create_iAsset_entry(owner_address, asset); + + // Ensure the asset is initialized in the receiver's LiquidityProvider struct + create_iAsset_entry(receiver_address, asset); + + let owner_iAsset_balance = fungible_asset::balance(owner_primary_store); + + assert!( + owner_iAsset_balance - owner_asset_entry.redeem_requested_iAssets >= iAsset_amount, + error::invalid_argument(EBALANCE_NOT_ENOUGH) + ); // to confirm on pending_withdraw_balance_iAsset + + //invoke transfer + //*owner_iAsset_balance -= iAsset_amount; + primary_fungible_store::transfer(account, asset, receiver_address, iAsset_amount); + + //let (receiver_iAsset_balance, _, _, _, _) = table::borrow_mut(&mut receiver_provider_ref.iAsset_table, asset_id); + //*receiver_iAsset_balance += iAsset_amount; + + update_rewards(owner_address, owner_asset_entry.user_reward_index, asset); + } +// +// public fun transfer_asset( +// asset_amount: u64, +// asset_id: u64, +// receiver_address: address, +// owner_address: address +// ) { +// let iAsset_amount = convertToiAssets(asset_amount, asset_id); +// +// let owner_provider_ref = borrow_global_mut(owner_address); +// let (owner_iAsset_balance, _, _, _, _) = table::borrow_mut(&mut owner_provider_ref.iAsset_table, asset_id); +// assert!(*owner_iAsset_balance >= iAsset_amount, 0); +// +// *owner_iAsset_balance -= iAsset_amount; +// +// if (!exists(receiver_address)) { +// initialize_LiquidityProvider(receiver_address); +// } +// add_asset_LiquidityProvider(asset_id, receiver_address); +// +// let receiver_provider_ref = borrow_global_mut(receiver_address); +// let (receiver_iAsset_balance, _, _, _, _) = table::borrow_mut(&mut receiver_provider_ref.iAsset_table, asset_id); +// *receiver_iAsset_balance += iAsset_amount; +// +// update_rewards(owner_address, asset_id); +// } +// + ///Function convertToAssets(shares, asset) + /// Description: Converts the asiAssets to assets before redemption + /// @param: shares - the iAsset amount getting previewed + /// @param: asset - the asset metadata + /// return u64 + public fun convertToAssets(asset_supply: u64, shares: u64, asset: Object): u64 { + + let iAsset_supply = get_iAsset_supply(asset); + + let asset_amount: u64; + + if (iAsset_supply == 0) { + asset_amount = shares; + } else { + asset_amount = (shares * asset_supply) / iAsset_supply; + }; + + asset_amount + } + + + fun reduce_asset_supply(asset: vector, amount: u64) acquires LiquidityTableItems { + let obj_address = asset_address(asset); + + let liquidity_ref = borrow_global_mut(obj_address); + + liquidity_ref.asset_supply = liquidity_ref.asset_supply - amount; + liquidity_ref.total_withdraw_requests = liquidity_ref.total_withdraw_requests + amount; + + } + + /// Function: update_desired_weight(desired_weight_vector, signer) + /// Purpose: Updates asset weights in the TotalLiquidity struct to align with current strategic objectives. + /// Description: + /// Ensure the sum of desired weights in the vector equals 1; if not, throw an error. + /// Update desired_weights for all assets in TotalLiquidity based on the vector. + + public fun update_desired_weight( + desired_weight_vector: vector, + account: &signer + ) acquires AssetTracker, LiquidityTableItems { + //Verify admin authority and existence of TotalLiquidity in Supra_framework. + assert!(signer::address_of(account) == @supra_framework, error::permission_denied(ENOT_OWNER)); + + let obj_address = get_storage_address(); + + let tracked_assets = borrow_global(obj_address); + + let num_assets = smart_table::length(&tracked_assets.assets); + //Check that desired_weight_vector length matches the number of assets in TotalLiquidity. + + assert!( + vector::length(&desired_weight_vector) == num_assets, error::invalid_argument(EWRONG_CWV_LENGTH) + ); + + let total_weight: u64 = 0; + + + vector::for_each(desired_weight_vector, | weight| {total_weight = total_weight + weight}); + + //Ensure the sum of desired weights in the vector equals 1; if not, throw an error. + assert!(total_weight == 1, error::invalid_argument(EWRONG_WEIGHT)); + + //Update desired_weights for all assets in TotalLiquidity based on the vector. + let index = 0; + smart_table::for_each_ref, bool>( + &tracked_assets.assets, + | key, _value| + { + let symbol = *key; + let table_obj = borrow_global_mut(asset_address(symbol)); + let new_desired_weight = vector::borrow(&desired_weight_vector, index); + table_obj.desired_weight = *new_desired_weight; + index = index + 1; + }); + + assert!(index == num_assets, error::invalid_state(EWRONG_ASSET_COUNT)); + } + + /// batch_update_desirability_score(desirability_score_vector, signer) + /// Purpose: Updates desirability scores for all assets in the TotalLiquidity struct. + public fun batch_update_desirability_score( + desirability_score_vector: vector, + signer: &signer + ) acquires AssetTracker, LiquidityTableItems { + //Verifies that the function caller is an authorized admin. + assert!(signer::address_of(signer) == @supra_framework, error::permission_denied(ENOT_OWNER)); + + let obj_address = get_storage_address(); + + let tracked_assets = borrow_global(obj_address); + + //Asserts that the length of desirability_score_vector matches the number of assets in TotalLiquidity. + let num_assets = smart_table::length(&tracked_assets.assets); + + assert!( + vector::length(&desirability_score_vector) == num_assets, error::invalid_argument(EWRONG_DESIRABILITY_SCORE_LEN) + ); + + //Adjusts the desirability_score for each asset in TotalLiquidity based on the new scores provided. + let index = 0; + smart_table::for_each_ref, bool>( + &tracked_assets.assets, + | key, _value| + { + let symbol = *key; + let table_obj = borrow_global_mut(asset_address(symbol)); + let new_desirability_score = vector::borrow(&desirability_score_vector, index); + table_obj.desirability_score = *new_desirability_score; + index = index + 1; + }); + } + + /// update_desirability_score(signer,desirability_score, assetID) + /// Purpose: Updates the desirability score of a specified asset within the TotalLiquidity struct of the Supra_framework. + public fun update_desirability_score( + signer: &signer, + desirability_score: u64, + asset_symbol: vector, + ) acquires AssetTracker, LiquidityTableItems { + //Verifies that the function caller is an authorized admin. + assert!(signer::address_of(signer) == @supra_framework, error::permission_denied(ENOT_OWNER)); + + let obj_address = get_storage_address(); + + let tracked_assets = borrow_global(obj_address); + //Confirms that the assetID corresponds to an asset within TotalLiquidity. + assert!( + smart_table::contains(&tracked_assets.assets, asset_symbol), + error::not_found(EASSET_NOT_PRESENT) + ); + + //Adjusts the desirability score for the asset based on the provided score and assetID. + let table_obj = borrow_global_mut(asset_address(asset_symbol)); + + table_obj.desirability_score = desirability_score; + } + + /// updates asset prices and calculates new supply metrics for collateral management in the system. + public(friend) fun calculate_nominal_liquidity() acquires TotalLiquidity, AssetTracker, LiquidityTableItems { + let total_nominal_liquidity: u64 = 0; + let total_liquidity_ref = borrow_global_mut(get_storage_address()); + let tracked_assets = borrow_global(get_storage_address()); + + + smart_table::for_each_ref, bool>( + &tracked_assets.assets, + | key, _value| + { + //let is_there: &bool = value; + let symbol = *key; + let table_obj = borrow_global_mut(asset_address(symbol)); + + // Fetch asset details and calculate new supply + let borrow_request = table_obj.total_borrow_requests; + let withdraw_request = table_obj.total_withdraw_requests; + let new_supply = table_obj.asset_supply + borrow_request - withdraw_request; + table_obj.asset_supply = new_supply; + + let (new_asset_price, _, _, _)= get_asset_price(symbol); + + let nominal_liquidity_of_asset = new_supply * new_asset_price; + + total_nominal_liquidity = total_nominal_liquidity + nominal_liquidity_of_asset; + + total_liquidity_ref.total_nominal_liquidity = total_nominal_liquidity; + + }); + } + + + public(friend) fun update_single_asset_supply(symbol: vector, new_supply: u64): u64 acquires LiquidityTableItems { + let table_obj = borrow_global_mut(asset_address(symbol)); + table_obj.asset_supply = table_obj.asset_supply + new_supply; + table_obj.asset_supply + + } + +///Calculate Total Renable amount +/// Calculates the total amount that is rentable to the users based on the current + + public fun calculate_total_rentable( + coefficient_k: u64, + coefficient_m: u64, + //coefficient_rho: u64, + min_collateralisation: u64, + max_collateralisation_first: u64, + max_collateralisation_second: u64 + ): u64 acquires AssetTracker, LiquidityTableItems, TotalLiquidity { + let tracked_assets = borrow_global(get_storage_address()); + + let total_rentable_amount: u64 = 0; + + smart_table::for_each_ref, bool>( + &tracked_assets.assets, + | key, _value| + { + //let is_there: &bool = value; + let symbol = *key; + let (asset_price, _, _, _)= get_asset_price(symbol); + let collateralisation_rate = calculate_collaterisation_rate( + symbol, + coefficient_k, + coefficient_m, + //coefficient_rho, + min_collateralisation, + max_collateralisation_first, + max_collateralisation_second + + ); + let table_obj = borrow_global_mut(asset_address(symbol)); + + total_rentable_amount = total_rentable_amount + ( + ((table_obj.asset_supply + table_obj.total_borrow_requests) - table_obj.total_withdraw_requests) / collateralisation_rate + ) * asset_price; + }); + total_rentable_amount + + } + + +///Calculate_collaterisation_rate +///Purpose: Calculates and updates the collateralization rate for a specified asset based on dynamic market weights and predefined coefficients. +///Calculation Method: The quality of collateral is crucial, yet the pursuit of specific assets must be weighed against diversification to mitigate both known +/// and unknown risks. We calculate the collateralisation rate for an asset (\rho^{{X}^i}_e) as follows: +/// \rho^ {X^i}_e = +/// \begin{cases} +/// \rho_{min} + (\rho_{max}^I - \rho_{min}) \cdot rounddown(\left(\frac{w^{X^{i^{\ast}}}_e-w^{{X}^i}_e}{w^{X^{i^{\ast}}}_e}\right)^k) & +/// \forall 0 \leq w^{X^i}_e \leq w^{X^{i^{\ast}}}_e\\ + +/// \rho_{min} + \left(\rho_{max}^{II} - \rho_{min}\right)\cdot rounddown(\left(\frac{w^{{X}^i}_e- w^{X^{i^{\ast}}}_e}{ 100 - w^{X^{i^{\ast}}}_e}\right)^m) & +/// \forall w^{X^{i^{\ast}}}_e < w^{X^i}_e \leq 100\\ +/// \end{cases} +/// Here, $\rho_{min}$ represents the minimum collateralization rate available for an asset, while $\rho_{max}^{I}$ and $\rho_{max}^{II}$ are the maximum +///collateralization rates for the asset, corresponding to the first and second intervals of the function, respectively. The parameters $k$ and $m$ describe +/// the relationship between the deviation of the asset weight in the collateral pool from desired weight and the collateralization rate for the asset. $w^{X^{i^{\ast}}}_e$ +///represents target weights of asset $X^i \neq 0$\footnote{$w^{X^{i^{\ast}}}_t = 0$ would mean that an asset cannot be admitted as collateral.}; +///and ${w^{{X}^i}_e}$ represents the weight of the asset in the basket in e-th epoch. This approach means that an excess or deficit of a specific asset influences the collateralisation rate. + + public fun calculate_collaterisation_rate( + symbol: vector, + coefficient_k: u64, + coefficient_m: u64, + min_collaterisation: u64, + max_collateralisation_first: u64, + max_collateralisation_second: u64 + ): u64 acquires TotalLiquidity, LiquidityTableItems { + + let total_liquidity_ref = borrow_global_mut(get_storage_address()); + + let (_, asset_supply, desired_weight, _) = get_liquidity_table_items(symbol); + + let (asset_price, _, _, _)= get_asset_price(symbol); + + let asset_weight = asset_supply * asset_price / total_liquidity_ref.total_nominal_liquidity; //to convert to f64 + + if (asset_weight <= desired_weight) { + let numerator = desired_weight - asset_weight; + let denominator = desired_weight; + let ratio = numerator / denominator; + let ratio_exp = math64::pow(ratio, coefficient_k); + + (min_collaterisation + ( + max_collateralisation_first - min_collaterisation) * ratio_exp) + } else { + let numerator = asset_weight - desired_weight; + let denominator = 100 - desired_weight; + let ratio = numerator / denominator; + let ratio_exp = math64::pow(ratio, coefficient_m); + + (min_collaterisation + + (max_collateralisation_second - min_collaterisation) * ratio_exp) + } + } + + public(friend) fun poel_update_olc_index() acquires TotalLiquidity { + let total_liquidity_ref = borrow_global_mut(get_storage_address()); + + total_liquidity_ref.current_olc_index = total_liquidity_ref.current_olc_index + 1; + + } + + public(friend) fun update_borrow_request(symbol: vector, value_to_add: u64) acquires LiquidityTableItems { + let obj_address = asset_address(symbol); + let liquidity_ref = borrow_global_mut(obj_address); + liquidity_ref.total_borrow_requests = liquidity_ref.total_borrow_requests + value_to_add; + } +} diff --git a/aptos-move/framework/supra-framework/sources/poel/iAsset.spec.move b/aptos-move/framework/supra-framework/sources/poel/iAsset.spec.move new file mode 100644 index 0000000000000..3314951f4aba7 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/iAsset.spec.move @@ -0,0 +1,6 @@ +spec supra_framework::iAsset { + + spec module { + pragma verify = true; + } +} \ No newline at end of file diff --git a/aptos-move/framework/supra-framework/sources/poel/poel.move b/aptos-move/framework/supra-framework/sources/poel/poel.move new file mode 100644 index 0000000000000..310254286ca63 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/poel.move @@ -0,0 +1,748 @@ +/** + This is the Proof of Efficiency Liquidity module used to delagate $supra to staking pools for users + who hold iEth by making deposits of ETH to the intralayer vault. It handles the creation of the iETH and distribution to the + corresponding addresses on supra + + General Flow: +Although these steps refer to ETH, the Proof of Efficient Liquidity (PoEL) approach can be applied similarly to other assets: +1. Users deposit WETH into the Intralayer Vault on the Ethereum blockchain. +2. The Supra cross-chain communication protocol forwards the deposit information to the PoEL (Proof of Efficient Liquidity) contract on the Supra chain. +3. This step moves the borrowing request to the pre-processing stage, where the request to rent Supra using ETH is registered. Additionally, the mechanism +pre-mints a special token called iETH to the user’s address. This iETH serves as a “coupon” representing the user’s share of the original ETH deposited in the Intralayer Vault. +4. Periodically, the protocol processes borrow requests, taking into account: + a. All available borrow requests + b. Price fluctuations of the underlying asset (based on an oracle price submitted to the PoEL contract) + c. Asset characteristics such as collateralization rates and desirability scores +The total amount lent through the PoEL contract depends on these factors. If the price of the asset (relative to $Supra) increases, the system rents and delegates more +tokens to the delegation pools. Conversely, if the price falls, the system reduces the lent amount and withdraws tokens from the pools. During delegation, +the rented tokens are evenly distributed across various delegation pools participating in the system. +5. After lending and delegation are finalized, users can convert their pre-minted iETH into actual assets that they can manage directly. +6. The delegated tokens earn staking rewards, which are distributed to iETH holders(for minted tokens) via the reward_distribution module. +7. Users can redeem iETH to reclaim ETH from the Intralayer Vault on the Ethereum blockchain. To do so, they submit a withdrawal request to the PoEL contract, +which burns their iETH and moves the request into the pre-processing stage. +8. Redemption requests are processed alongside new borrow requests. If the remeption requested amount > borrow requested amount, this process decreases the overall rented +amount and unstakes the corresponding tokens from the delegation pools. +After redemption requests have been processed, users can withdraw their ETH from the system, receiving the assets on the Ethereum blockchain. + */ + +module supra_framework::poel { + use aptos_std::object::{Self, ExtendRef}; + use aptos_std::vector; + use aptos_std::signer; + use aptos_std::smart_table::{Self, SmartTable}; + use aptos_std::error; + use supra_framework::iAsset::{ + mint_iAsset, premint_iAsset, update_asset_price_supply, + update_single_asset_supply, get_liquidity_table_items, asset_metadata, + calculate_collaterisation_rate, calculate_total_rentable, get_assets, get_total_liquidity, + get_asset_price, poel_update_olc_index, update_borrow_request + }; + use supra_framework::reward_distribution::update_reward_index; + use supra_framework::pbo_delegation_pool::{unlock, add_stake, withdraw, get_pending_withdrawal, get_stake}; + use supra_framework::coin; + use supra_framework::supra_coin::SupraCoin; + + use supra_framework::timestamp; + + /// thrown when the allowed signer is not the caller (owner) + const ENOT_OWNER: u64 = 1; + + /// thrown when the wrong asset weight gets calculated + const EWRONG_WEIGHT: u64 = 2; + + /// thrown when the length of the collaterixation weight vector does not match table entries + const EWRONG_CWV_LENGTH: u64 = 3; + + /// thrown when the length of the desirability_score_vector does not match table entries + const EWRONG_DESIRABILITY_SCORE_LEN: u64 = 4; + + /// thrown If coefficient_m < coefficient_k + const ECOEFM_LT_COEFK: u64 = 5; + + /// thrown when insificient balance + const EINSUFICIENT_CALLER_BALANCE: u64 = 6; + + /// thrown when the reward balance is wrong + const EWRONG_REWARD_BALANCE: u64 = 7; + + /// thrown when the caller s not the bridge address + const EBRIDGE_NOT_PRESENT: u64 = 8; + + /// thrown when calling a second initialization of the bridges + const EBRIDGE_INITIALIZED: u64 = 9; + + /// seed for the total liquidity object for easy access + const POEL_STORAGE_ADDRESS: vector = b"PoELStorageGlobal"; + + + //Is applied for the dynamic adjustment of the system's operational parameters. It allows for flexible management of financial metrics, adapting to market conditions or strategic shifts in policy. + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct MutableParameters has key { + coefficient_k: u64, + coefficient_m: u64, + coefficient_rho: u64, + min_collateralisation: u64, + number_of_Epochs_for_extra_Reward_Allocation: u64, + length_of_lockup_cycle: u64, + max_collateralisation_first: u64, + max_collateralisation_second: u64, + reward_reduction_rate: u64, + reward_distribution_address: address, + } + + + struct StakingPoolMap has store{ + //The amount of Supra delegated to the validator at this pool. + delegated_amount: u64, + //Amount of tokens that are pending deactivation but are currently inactive. + //pending_inactive_balance: u64 + } + + //Description: This struct serves as a central repository for tracking delegated assets in various staking pools, providing essential data for managing staking operations and calculating borrowing limits based on active and inactive balances within the system. + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct DelegatedAmount has key { + /// Maps StakingPoolID to tuple StakingPoolMap + staking_pool_mapping: SmartTable, + /// Cumulative amount of Supra delegated across all staking pools + total_delegated_amount: u64, + /// Total amount of Supra pending deactivation + pending_inactive_balance: u64, + /// Total amount of Supra that could be borrowed from the PoEL contract + total_borrowable_amount: coin::Coin, + /// Index of the lockup cycle when the PoEL last withdrew Supra tokens + withdrawal_OLC_index: u64, + /// Represents the earned staking rewards that will be available for withdrawal in the next lockup cycle + withdrawable_rewards: u64, + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct AdminManagement has key { + /// Total amount of Supra tokens requested by the admin for withdrawal as part of the process to reduce the rentable amount + withdraw_requested_assets: u64, + /// Index of the last withdrawal request submitted by the admin + withdraw_OLC_index: u64, + /// Records the address of the delegation pool that is being replaced. + replaced_delegation_pool: address, + /// Records the index of the last pool replacement request submitted by admin + change_Delegation_pool_OLC_index: u64, + /// Records the address of the admin that can amend some of the mutable parameters to optimise the system such as total + /// rentable amount + admin_address: address, + /// Stores the address where the extra Supra would be depositted after admin decreases the total rentable amount + withdrawal_address: address, + reward_allocation_OLC_index: u64, + } + + /// Stores the bridge address used to verify that a Supra renting request is submitted by a valid address. + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct BridgeAddresses has key { + bridge_addresses: vector
, + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct PoelControler has key { + extend_ref: ExtendRef, + } + + /// In addition to staking rewards, contributors in the Intralayer vaults (iAsset holders) + /// can also earn extra $Supra rewards that are submitted by admin and other users. + /// This struct is designed to record the budget for these additional reward distributions. + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct ExtraReward has key { + /// Total budget for rewards earmarked for distribution over multiple epochs. + multiperiod_extra_rewards: u64, + /// he current balance of these rewards after previous distributions. + multiperiod_reward_balance: u64, + /// The current balance of rewards that will be immediately applied in the next lockup cycle (these rewards are asset-specific). + singleperiod_reward_balance: u64, + } + + + fun init_module(account: &signer) { + let constructor_ref = &object::create_named_object(account, POEL_STORAGE_ADDRESS); + + let obj_signer = &object::generate_signer(constructor_ref); + + let extend_ref = object::generate_extend_ref(constructor_ref); + + move_to(obj_signer, PoelControler { + extend_ref: extend_ref, + }); + + move_to(obj_signer, DelegatedAmount { + staking_pool_mapping: smart_table::new(), + total_delegated_amount: 0, + pending_inactive_balance: 0, + total_borrowable_amount: coin::zero(), + withdrawal_OLC_index: 0, + withdrawable_rewards: 0 + }); + + move_to(obj_signer, MutableParameters { + coefficient_k: 0, + coefficient_m: 0, + coefficient_rho: 0, + min_collateralisation: 0, + number_of_Epochs_for_extra_Reward_Allocation: 0, + length_of_lockup_cycle: 0, + max_collateralisation_first: 0, + max_collateralisation_second: 0, + reward_reduction_rate: 0, + reward_distribution_address: @0x0, + }); + + move_to(obj_signer, AdminManagement { + withdraw_requested_assets: 0, + withdraw_OLC_index: 0, + replaced_delegation_pool: @0x0, + change_Delegation_pool_OLC_index: 0, + admin_address: signer::address_of(account), + withdrawal_address: @0x0, + reward_allocation_OLC_index: 0, + + }); + + move_to(obj_signer, BridgeAddresses { + bridge_addresses: vector::empty(), + //delegation_pools: vector::empty(), + }); + + move_to(obj_signer, ExtraReward { + multiperiod_extra_rewards:0, + multiperiod_reward_balance:0, + singleperiod_reward_balance:0, + }); + + } + + + \\\ The function initializes and registers the bridge addresses and original delegation pools used in the system + public fun initialize_Bridge_Pools(bridge_addresses: vector
, delegation_pools: vector
) acquires BridgeAddresses, DelegatedAmount { + let obj_address = get_poel_storage_address(); + + let bridge_obj = borrow_global_mut(obj_address); + + bridge_obj.bridge_addresses = bridge_addresses; + + assert!(vector::is_empty(&bridge_obj.bridge_addresses), error::invalid_state(EBRIDGE_INITIALIZED)); + vector::for_each_ref
(&delegation_pools, |value| { + let pool: address = *value; + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + add_staking_pool(delegated_amount_ref, pool, 0); + }); + } + + #[view] + public fun get_poel_storage_address(): address { + object::create_object_address(&@supra_framework, POEL_STORAGE_ADDRESS) + } + + + fun get_obj_signer(): signer acquires PoelControler { + let obj_address = get_poel_storage_address(); + + let extend_ref = borrow_global(obj_address); + + object::generate_signer_for_extending(&extend_ref.extend_ref) + + } + + + fun add_staking_pool( + delegated_amount: &mut DelegatedAmount, + pool_address: address, + delegated_amount_value: u64, + //pending_inactive_balance: u64 + ) { + smart_table::add( + &mut delegated_amount.staking_pool_mapping, + pool_address, + StakingPoolMap { + delegated_amount: delegated_amount_value, + //pending_inactive_balance: pending_inactive_balance + } + ); + //delegated_amount.total_delegated_amount = (delegated_amount.total_delegated_amount + delegated_amount_value); + //delegated_amount.pending_inactive_balance = (delegated_amount.pending_inactive_balance + pending_inactive_balance); + } + + + //public fun submit_withdraw_request( + // request_table: &mut BorrowWithdrawRequest, + // asset: Object, + // withdraw_amount: u64 + //) { + // let withdraw_items = table::borrow_mut(&mut request_table.requests, asset); + // withdraw_items.total_withdraw_requests = (withdraw_items.total_withdraw_requests + withdraw_amount); + //} + + //public fun get_requests( + // request_table: &BorrowWithdrawRequest, + // asset: Object + //): &BorrowRequestTableItems { + // table::borrow(&request_table.requests, asset) + //} + +/// Enables admin to configure the mutable parameters used for various processes, such as collateralization rate calculations and reward distribution. + entry fun set_parameters( + account: &signer, + coefficient_k: u64, + coefficient_m: u64, + coefficient_rho: u64, + min_collateralisation: u64, + number_of_Epochs_for_extra_Reward_Allocation: u64, + length_of_lockup_cycle: u64, + max_collateralisation_first: u64, + max_collateralisation_second: u64, + reward_reduction_rate: u64, + reward_distribution_address: address, + ) acquires MutableParameters, AdminManagement { + assert!(signer::address_of(account) == get_admin_address(), error::permission_denied(ENOT_OWNER)); + + let mutable_params_ref = borrow_global_mut(get_poel_storage_address()); + + mutable_params_ref.coefficient_k = coefficient_k; + mutable_params_ref.coefficient_m = coefficient_m; + mutable_params_ref.coefficient_rho = coefficient_rho; + mutable_params_ref.min_collateralisation = min_collateralisation; + mutable_params_ref.number_of_Epochs_for_extra_Reward_Allocation = number_of_Epochs_for_extra_Reward_Allocation; + mutable_params_ref.length_of_lockup_cycle = length_of_lockup_cycle; + mutable_params_ref.max_collateralisation_first = max_collateralisation_first; + mutable_params_ref.max_collateralisation_second = max_collateralisation_second; + mutable_params_ref.reward_reduction_rate = reward_reduction_rate; + mutable_params_ref.reward_distribution_address = reward_distribution_address; + } + + /// Finction: unlock_tokens(supra_amount) + /// Desctriptio: Unlocks token from the all delegation pools involved in the system + /// @param: supra_amount - u64 + fun unlock_tokens(supra_amount: u64) acquires DelegatedAmount, PoelControler { + let delegated_amount = borrow_global_mut(get_poel_storage_address()); + + let pool_num = smart_table::length(&delegated_amount.staking_pool_mapping); + let remainder = supra_amount % pool_num; + let adjusted_supra_amount = supra_amount - remainder; + + let per_pool_unlock_amount = adjusted_supra_amount / pool_num; + + let total_unlocked_amount = 0; + + + smart_table::for_each_mut( + &mut delegated_amount.staking_pool_mapping, + | key, _value| + { + let pool_address = *key; + let (active_stake, inactive_stake, _) = get_stake(pool_address, get_poel_storage_address()); + + if (active_stake >= per_pool_unlock_amount) { + + total_unlocked_amount = total_unlocked_amount + per_pool_unlock_amount; + + unlock(&get_obj_signer(), pool_address, per_pool_unlock_amount); + } else if ( active_stake < per_pool_unlock_amount) { + total_unlocked_amount = total_unlocked_amount + active_stake; + + unlock(&get_obj_signer(), pool_address, per_pool_unlock_amount); + }; + if (inactive_stake > 0) { + withdraw(&get_obj_signer(), pool_address, inactive_stake); // Assuming withdraw is defined in delegation_pool.move + }; + + } + ); + + //let delegated_amount = borrow_global_mut(DELEGATED_AMOUNT_ADDRESS); + delegated_amount.total_delegated_amount = delegated_amount.total_delegated_amount - total_unlocked_amount; + } + + /// Delegates token to the all delegation pools involved in the system + fun delegate_tokens(supra_amount: u64) acquires DelegatedAmount, PoelControler { + let pool_table = borrow_global_mut(get_poel_storage_address()); + + let pool_num = smart_table::length(&pool_table.staking_pool_mapping); + + let remainder = supra_amount % pool_num; + let adjusted_supra_amount = supra_amount - remainder; + + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + + smart_table::for_each_mut( + &mut delegated_amount_ref.staking_pool_mapping, + | key, value| + { + let pool_address = *key; + let staking_map: &mut StakingPoolMap = value; + + add_stake(&get_obj_signer(), pool_address, adjusted_supra_amount); + + staking_map.delegated_amount = staking_map.delegated_amount + adjusted_supra_amount; + + }); + + delegated_amount_ref.total_delegated_amount = delegated_amount_ref.total_delegated_amount + adjusted_supra_amount; + } + + /// Enables the withdrawal of unlocked tokens that are in an inactive state from delegation pools. + public fun withdraw_tokens() acquires DelegatedAmount, PoelControler { + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + + // Preconditions + //assert!(delegated_amount_ref.withdrawal_OLC_index < current_olc_index, 0); + + //assert!(delegated_amount_ref.pending_inactive_balance > 0, 1); + + // Token Withdrawal Process + smart_table::for_each_ref( + &delegated_amount_ref.staking_pool_mapping, + | key, _value| + { + let pool_address = *key; + let (_, pending_withdrawal) = get_pending_withdrawal(pool_address, signer::address_of(&get_obj_signer())); // Assuming signer is the delegator addr + // Execute the withdrawal using the withdraw function from delegation_pool.move + if (pending_withdrawal > 0) { + withdraw(&get_obj_signer(), pool_address, pending_withdrawal); + }; + + }); + let (current_olc_index, _) = get_total_liquidity(); + + delegated_amount_ref.withdrawal_OLC_index = current_olc_index; + } + + +/// Allocate Rewards Function +///Description: The function does the following: +/// a. computes the total amount of rewards earned +/// b. computes out of the total earned rewards which part is allocable for different assets, +/// b.1 Calculate the loan granted for an asset: +/// L^a_e = \frac{S^a_e \cdot P^a_e}{\rho} +/// where $S^a_e$ is the amount of asset $a$ submitted to the vault at the epoch $e$ and $P^a_e$ is +///the price of asset $a$ (in terms of \$Supra) at the epoch $e$ submitted by an Oracle, $\rho$ is collaterisation rate. +/// b.2 Calculate the reward distributed for an asset($DR^a_e$). +///DR^a_e = \frac{L^a_e \cdot \mathcal{W}^a_e}{\sum^N_{j=0} L^j_e \cdot \mathcal{W}^j_e} \cdot R_e +///where R_e is total rewards earned from the staking and other distributable rewards, $N$ is the length of the set of asset $\mathbf{A}$ +///supported by the system, $\mathcal{W}^a_e$ is the desirability parameter for asset $a$. +///c. updates reward index for the asset +/// R_e consists of three components: +/// a. **Staking Rewards:** The rewards earned from staking, adjusted by a reward reduction rate(RRR). RRR can be modified by admin decisions—either +///to allocate a portion of rewards elsewhere or to implement logic that distributes additional rewards(accrued as cut from earned staking rewards) to market participant +///(e.g., liquidity providers) who assume greater roles in the system. +/// b. **Administrative Rewards:** Extra rewards in $Supra can be submitted by the admin, which are then distributed to iAsset +///holders over multiple operating cycles (not asset-specific). +/// c. **Asset-Specific Rewards:** Any market participant can provide rewards targeted at specific assets, +/// which are distributed to the holders of those iAssets. It is distributed directly in the next operating cycle +/// If the Intralayer vaults migrate into AMM pools, the fees generated can be used by the AMM contract to distribute asset-specific rewards to iAsset holders, +///who effectively act as liquidity providers (LPs). +///R_e = rounddown(withdrawable_rewards * (1 - reward_reduction_rate)) + min(adminstrative rewards / Number_of_Epochs_for_Extra_Reward_Allocation, multiperiod_reward_balance) +///the asset specific distributable rewards can be computed as +///asset_specific_rewards = DR^a_e + asset-specific rewards + + + public fun allocate_rewards() acquires DelegatedAmount, MutableParameters, PoelControler { + let total_reward_earned: u64 = 0; + + + //let delegate_num_ref = borrow_global(get_poel_storage_address()); + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + + smart_table::for_each_ref( + &delegated_amount_ref.staking_pool_mapping, + | key, _value| + { + let pool_address = *key; + // Calculate the total stake by summing values from the pool + let (active_stake, inactive_stake, _) = get_stake(pool_address, get_poel_storage_address()); + + let total_stake = active_stake + inactive_stake; + + // Compute the rewards for the pool and update the total_reward_earned + let pool_reward = total_stake - delegated_amount_ref.total_delegated_amount; + total_reward_earned = total_reward_earned + pool_reward; + + // Unlock the calculated total_reward_earned for each pool + unlock(&get_obj_signer(), pool_address, pool_reward); + + }); + + + let (_, total_nominal_value) = get_total_liquidity(); + + let tracked_assets = get_assets(); + + let mutable_params_ref = borrow_global(get_poel_storage_address()); + let coefficient_k = mutable_params_ref.coefficient_k; + let coefficient_m = mutable_params_ref.coefficient_m; + let min_collateralisation = mutable_params_ref.min_collateralisation; + let max_collateralisation_first = mutable_params_ref.max_collateralisation_first; + let max_collateralisation_second = mutable_params_ref.max_collateralisation_second; + + vector::for_each_ref>( + &tracked_assets, + | key| + { + //let is_there: &bool = value; + let symbol = *key; + + let collateralisation_rate = calculate_collaterisation_rate( + symbol, + coefficient_k, + coefficient_m, + //coefficient_rho, + min_collateralisation, + max_collateralisation_first, + max_collateralisation_second + ); + let (_, asset_supply, _, desirability_score) = get_liquidity_table_items(symbol); + let (asset_price, _decimal, _timestamp, _round) = get_asset_price(symbol); + + let available_reward_for_asset = ( + (desirability_score * asset_price * asset_supply / collateralisation_rate) / total_nominal_value + ) * total_reward_earned; + + update_reward_index(asset_metadata(symbol), available_reward_for_asset, asset_supply, desirability_score); + + }); + } + + + /// This function recalculates and updates(delegates or unlocks) the total amount of + /// Supra that is borrowed and delegated based on the current asset prices, supplies, collateralization rates, borrow and redeem requests. + public fun update_rented_amount() acquires DelegatedAmount, MutableParameters, PoelControler { + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + let current_delegated_amount = delegated_amount_ref.total_delegated_amount; + + calculate_nominal_liquidity(); + + let mutable_params_ref = borrow_global(get_poel_storage_address()); + let coefficient_k = mutable_params_ref.coefficient_k; + let coefficient_m = mutable_params_ref.coefficient_m; + let min_collateralisation = mutable_params_ref.min_collateralisation; + let max_collateralisation_first = mutable_params_ref.max_collateralisation_first; + let max_collateralisation_second = mutable_params_ref.max_collateralisation_second; + + + let change_of_rented_amount: u64 = calculate_total_rentable( + coefficient_k, + coefficient_m, + min_collateralisation, + max_collateralisation_first, + max_collateralisation_second + ); + + let change_of_borrowed_amount: u64 = 0; + let total_borrowable_amount = coin::value(&delegated_amount_ref.total_borrowable_amount); + + if (change_of_rented_amount > current_delegated_amount) { + let difference = change_of_rented_amount - current_delegated_amount; + if (difference <= total_borrowable_amount) { + change_of_borrowed_amount = difference; + } else { + change_of_borrowed_amount = total_borrowable_amount; + } + } else { + change_of_borrowed_amount = 0; + }; + + if (change_of_borrowed_amount == 0) { + return + } else if (change_of_borrowed_amount > 0) { + delegate_tokens(change_of_borrowed_amount); + } else if (change_of_borrowed_amount < 0) { + unlock_tokens(change_of_borrowed_amount); //should be negative? + } + } + + /// Enables admin to increase the rentable Supra amount by transferring funds to the PoEL contract. + public fun increase_rentable_amount(account: &signer, amount: u64) acquires DelegatedAmount, AdminManagement { + assert!(signer::address_of(account) == get_admin_address(), error::permission_denied(ENOT_OWNER)); + + let coins = coin::withdraw(account, amount); + + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + + //increase the rentable abount + coin::merge(&mut delegated_amount_ref.total_borrowable_amount, coins); + } + + /// Decreases the amount of rentable tokens available in the PoEL contract. + public fun decrease_rentable_amount(account: &signer, amount: u64) acquires DelegatedAmount, AdminManagement, PoelControler { + let admin = get_admin_address(); + assert!(signer::address_of(account) == admin, error::permission_denied(ENOT_OWNER)); + + let admin_management_ref = borrow_global_mut(get_poel_storage_address()); + + let (current_olc_index, _) = get_total_liquidity(); // Assuming a function to get the current OLC index + + // Withdrawal Index Check + if (admin_management_ref.withdraw_OLC_index < current_olc_index && admin_management_ref.withdraw_OLC_index != 0) { + let withdraw_requested_assets = admin_management_ref.withdraw_requested_assets; + + // Send withdraw_requested_assets amount of Supra from the PoEL contract to the admin + coin::transfer(&get_obj_signer(), admin_management_ref.admin_address, withdraw_requested_assets); + + admin_management_ref.withdraw_requested_assets = 0; + }; + + let unmut_delegated_amount_ref = borrow_global(get_poel_storage_address()); + let supra_balance = coin::value(&unmut_delegated_amount_ref.total_borrowable_amount); + + + // Token Movement + if (supra_balance >= amount) { + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + //extract the amount of supra + let extracted_supra = coin::extract(&mut delegated_amount_ref.total_borrowable_amount, amount); + + // deposit the specified amount of Supra from the PoEL contract to the admin's account + coin::deposit(admin, extracted_supra); + + } else { + //let coin_value = coin::value(&unmut_delegated_amount_ref.total_borrowable_amount); + if (amount > supra_balance) { + abort(1) // Throw an error if requested amount exceeds total borrowable amount + } else { + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + + //extract the amount of supra + //double check this logic. the extracted supra could be unsed + let extracted_supra = coin::extract(&mut delegated_amount_ref.total_borrowable_amount, amount); + // Move the entire Supra balance from the PoEL contract to the admin's account + coin::deposit(admin, extracted_supra); + + let shortfall = amount - supra_balance; + + unlock_tokens(shortfall); + + + } + }; + + admin_management_ref.withdraw_OLC_index = current_olc_index; + } + + /// After a user submits assets to the Intralayer vaults, this function enables the creation of a borrow request via the bridge. + /// The system then periodically collects these borrow requests and, based on them, allocates $Supra to + /// liquidity providers of the Intralayer vaults, delegating it to the delegation pools on their behalf. + public fun borrow_request( + account: &signer, + asset_symbol: vector, + asset_amount: u64, + receiver_address: address + ) acquires BridgeAddresses { + + let bridge_addresses = borrow_global(get_poel_storage_address()); + + let bridge_exists = vector::contains
(&bridge_addresses.bridge_addresses,&signer::address_of(account)); + assert!(bridge_exists, error::permission_denied(EBRIDGE_NOT_PRESENT)); + + update_borrow_request(asset_symbol, asset_amount); + + let (_, asset_supply, _, _) = get_liquidity_table_items(asset_symbol); + premint_iAsset(asset_amount, asset_symbol, receiver_address, asset_supply); + } + + + /// The function calculates the appropriate borrowable amount based on the type and quantity of the asset deposited into the Intralayer vaults. + /// It also manages the delegation of this borrowable amount across different staking pools. + public fun borrow( + asset_symbol: vector, + asset_amount: u64, + receiver_address: address + ) acquires DelegatedAmount , MutableParameters, PoelControler{ + let (asset_price, _decimal, _timestamp, _round) = get_asset_price(asset_symbol); + + let new_balance_asset = update_single_asset_supply(asset_symbol, asset_amount); + + let mutable_params_ref = borrow_global(get_poel_storage_address()); + let coefficient_k = mutable_params_ref.coefficient_k; + let coefficient_m = mutable_params_ref.coefficient_m; + //let coefficient_rho = mutable_params_ref.coefficient_rho; + let min_collateralisation = mutable_params_ref.min_collateralisation; + let max_collateralisation_first = mutable_params_ref.max_collateralisation_first; + let max_collateralisation_second = mutable_params_ref.max_collateralisation_second; + let nominal_value = new_balance_asset * asset_price; + let collateralisation_rate = calculate_collaterisation_rate( + asset_symbol, + coefficient_k, + coefficient_m, + min_collateralisation, + max_collateralisation_first, + max_collateralisation_second + ); + let borrowed_amount = (asset_amount / collateralisation_rate) * asset_price; + + delegate_tokens(borrowed_amount); + + mint_iAsset(receiver_address, asset_symbol); + + let delegated_amount_ref = borrow_global_mut(get_poel_storage_address()); + delegated_amount_ref.total_delegated_amount = delegated_amount_ref.total_delegated_amount + borrowed_amount; + } + + \\\ Function is applied to increment the OLC index + fun update_olc_index(olc_index_update_timestep: u64) acquires MutableParameters { + let mutable_params_ref = borrow_global(get_poel_storage_address()); + + let time_now = timestamp::now_seconds(); + + if ((time_now - olc_index_update_timestep) >= mutable_params_ref.length_of_lockup_cycle ) { + poel_update_olc_index() }; + + } + \\\ This function allows to add extra rewards to the system, which will be distributed + \\\ across multiple periods. + entry fun add_multiperiod_extra_rewards(amount: u64, account: &signer) acquires ExtraReward { + let extra_reward_ref = borrow_global_mut(get_poel_storage_address()); + + assert!(extra_reward_ref.multiperiod_reward_balance == 0, error::invalid_state(EWRONG_REWARD_BALANCE)); + let user_balance = coin::balance(signer::address_of(account)); + + assert!(user_balance >= amount, error::invalid_argument(EINSUFICIENT_CALLER_BALANCE)); + + extra_reward_ref.multiperiod_extra_rewards = amount; + extra_reward_ref.multiperiod_reward_balance = amount; + } + + \\\ The function allows anyone to add extra rewards to the system, which will be distributed + \\\ in the next OLC. + entry fun add_singleperiod_extra_rewards(amount: u64, account: &signer) acquires ExtraReward { + let user_balance = coin::balance(signer::address_of(account)); + assert!(user_balance >= amount, error::invalid_argument(EINSUFICIENT_CALLER_BALANCE)); + let extra_reward_ref = borrow_global_mut(get_poel_storage_address()); + + extra_reward_ref.singleperiod_reward_balance = amount; + } + + \\\ Enables change of admin + entry fun change_admin(account: &signer, new_address: address) acquires AdminManagement { + let admin_management_ref = borrow_global_mut(get_poel_storage_address()); + + assert!(admin_management_ref.admin_address == signer::address_of(account), error::permission_denied(ENOT_OWNER)); + + admin_management_ref.admin_address = new_address; + } + + \\\ Enables updating the withdrawal address, which is where funds are sent when the admin decreases the total rentable amount. + entry fun change_withdrawal_address(account: &signer, new_address: address) acquires AdminManagement { + let admin_management_ref = borrow_global_mut(get_poel_storage_address()); + + assert!(admin_management_ref.admin_address == signer::address_of(account), error::permission_denied(ENOT_OWNER)); + + admin_management_ref.withdrawal_address = new_address; + } + + fun get_admin_address(): address acquires AdminManagement { + let admin_management_ref = borrow_global(get_poel_storage_address()); + admin_management_ref.admin_address + + } + + + + + #[test(creator = @store)] + fun test_init(creator: &signer) { + init_module(creator); + } +} diff --git a/aptos-move/framework/supra-framework/sources/poel/poel.spec.move b/aptos-move/framework/supra-framework/sources/poel/poel.spec.move new file mode 100644 index 0000000000000..19489d10cab76 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/poel.spec.move @@ -0,0 +1,6 @@ +spec supra_framework::poel { + + spec module { + pragma verify = true; + } +} \ No newline at end of file diff --git a/aptos-move/framework/supra-framework/sources/poel/reward_distribution.move b/aptos-move/framework/supra-framework/sources/poel/reward_distribution.move new file mode 100644 index 0000000000000..ebb67841f2766 --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/reward_distribution.move @@ -0,0 +1,147 @@ +/// This is the reward distribution module +/// it manages the reward metrics of the Proof of Efficiency Liquidity module and iAssets module +/// Its main purposes include: updating reward indexes, calculating user rewards, and updating user rewards + + +module supra_framework::reward_distribution { + use supra_framework::table; + use supra_framework::object::{Self, Object}; + use supra_framework::error; + + use aptos_std::fungible_asset::Metadata; + use aptos_std::primary_fungible_store; + friend supra_framework::poel; + + const ENO_AVAILABLE_DESIRABLE_LIQUIDITY: u64 = 1; + + const DISTRIBUTABLE_REWARDS: vector = b"DistributableRewards"; + + struct TotalLiquidityProvidedItems has store, copy { + ///reward_index_asset: Index tracking the reward distribution of the asset (u64). + reward_index_asset: u64, + ///available_rewards_for_asset: tracks the available rewards that are distributable for each asset + available_rewards_for_asset: u64, + } + + #[resource_group_member(group = supra_framework::object::ObjectGroup)] + struct DistributableRewards has key { + // Maps AssetID to TotalLiquidityProvidedItems + total_liquidity_provided: table::Table, TotalLiquidityProvidedItems>, + } + + + fun init_module(account: &signer) { + let constructor_ref = &object::create_named_object(account, DISTRIBUTABLE_REWARDS); + + let obj_signer = &object::generate_signer(constructor_ref); + move_to(obj_signer, DistributableRewards { + total_liquidity_provided: table::new(), + }); + } + + ///Update_reward_index(asseID, rewards): + ///Purpose: Updates the reward index for an asset based on newly distributed rewards. The function is applied for each asset after rewards are withdrawn from + ///the delegation pools at the end of each OLC, updating the asset's global reward index + public(friend) fun update_reward_index( + asset: Object, + rewards: u64, + asset_supply: u64, + desirability_score: u64, + ) acquires DistributableRewards { + if (asset_supply == 0 || desirability_score == 0) { + abort(error::aborted(ENO_AVAILABLE_DESIRABLE_LIQUIDITY)) + }; + let reward_index_increase = rewards / asset_supply; + + let distributable_rewards_ref = borrow_global_mut( + object::create_object_address(&@supra_framework, DISTRIBUTABLE_REWARDS) + ); + let distributable_table_item = table::borrow_mut( + &mut distributable_rewards_ref.total_liquidity_provided, + asset + ); + + distributable_table_item.reward_index_asset = distributable_table_item.reward_index_asset + reward_index_increase; + + table::add( + &mut distributable_rewards_ref.total_liquidity_provided, + asset, + *distributable_table_item + ); + } + + + ///Purpose: Computes the rewards for a user based on assets held on their account for some period of time and asset specific global reward index(at the point of the function call) and + /// user specific reward(updated for the user at the point of the last asset balance change). + ///Calculation: To determine the total amount of rewards r_u allocated to user u for maintaining their iAsset a holdings from t_k to t_n, we use the following formula: + /// r_u = D_u^a \cdot \left(\sum_{i=0}^{e_n} \frac{R_i^a}{D_i^a} - \sum_{i=0}^{e_k} \frac{R_i^a}{D_i^a}\right) + ///Here, R_i^a represents the total rewards distributed to all holders of iAsset a at the i-th timestep, and D_i^a is the total supply + ///of iAsset a at the same timestep. D_u^a represent the total holdings of the iAsset a owned by user u. Over the interval from t_k to t_n, + /// D_u^a remains constant in the user's wallet.Since block rewards are distributed at the end of each epoch, let e_n denote the final reward + /// distribution timestep immediately before t_n, and let e_k denote the reward distribution timestep that occurred just before t_k.\\\\ + /// We define the general reward index for asset a as + /// \sum_{i=0}^{e_n} \frac{R_i^a}{D_i^a}, + /// and the user's reward index for asset a as + /// \sum_{i=0}^{e_k} \frac{R_i^a}{D_i^a}. + /// Both indices are asset-specific and pertain to the given asset a. + + public fun calculate_rewards( + user_address: address, + user_reward_index: u64, + asset: Object + ): u64 acquires DistributableRewards { + let iAsset_balance = primary_fungible_store::balance(user_address, asset); + + let distributable_rewards_ref = borrow_global( + object::create_object_address(&@supra_framework, DISTRIBUTABLE_REWARDS) + ); + let reward_index_asset = table::borrow( + &distributable_rewards_ref.total_liquidity_provided, + asset + ).reward_index_asset; + + //Compute Rewards using the formula: Rewards= iAssetBalance * (reward_index_asset - rewardIndexOf[_account]) + + let rewards = iAsset_balance * (reward_index_asset - user_reward_index); + + rewards + } + + + ///Purpose: Updates and distributes rewards for a specific asset to an account. + public fun update_rewards( + user_address: address, + user_reward_index: u64, + asset: Object + ): u64 acquires DistributableRewards { + let new_user_reward_index: u64 = 0; + + + let distributable_rewards_ref = borrow_global( + object::create_object_address(&@supra_framework, DISTRIBUTABLE_REWARDS) + ); + + let distributable_rewards_table_item = table::borrow(&distributable_rewards_ref.total_liquidity_provided, asset); + let reward_index_asset = distributable_rewards_table_item.reward_index_asset; + + if (user_reward_index == new_user_reward_index) { + new_user_reward_index = reward_index_asset; + } else { + let _distributable_rewards = calculate_rewards(user_address, user_reward_index, asset); + + //assert!(PoEL::has_sufficient_tokens(distributable_rewards), INSUFFICIENT_TOKENS_ERROR); + + //PoEL::transfer_tokens(distributable_rewards, user_address); + + new_user_reward_index = reward_index_asset; + }; + new_user_reward_index + } + + + #[test(creator = @supra_framework)] + fun test_init(creator: &signer) { + init_module(creator); + } + +} diff --git a/aptos-move/framework/supra-framework/sources/poel/reward_distribution.spec.move b/aptos-move/framework/supra-framework/sources/poel/reward_distribution.spec.move new file mode 100644 index 0000000000000..e27256c2167ad --- /dev/null +++ b/aptos-move/framework/supra-framework/sources/poel/reward_distribution.spec.move @@ -0,0 +1,6 @@ +spec supra_framework::reward_distribution { + + spec module { + pragma verify = true; + } +} \ No newline at end of file