diff --git a/src/contract.rs b/src/contract.rs index 0d5708f..79c71ec 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,3 +1,7 @@ +use crate::delegated::{ + claim_rewards_delegated, delegate, delegated_protocol_fee_claim, undelegate, + update_excluded_fee_pair, +}; use crate::error::ContractError; use crate::helpers::{ get_token_supply, query_app_exists, query_extended_pair_by_app, query_get_asset_data, @@ -5,10 +9,10 @@ use crate::helpers::{ }; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, SudoMsg}; use crate::state::{ - Delegation, DelegationInfo, EmissionVaultPool, Proposal, Vote, VotePair, ADMIN, - APPCURRENTPROPOSAL, BRIBES_BY_PROPOSAL, COMPLETEDPROPOSALS, CSWAP_ID, DELEGATED, - DELEGATION_INFO, EMISSION, EMISSION_REWARD, MAXPROPOSALCLAIMED, PROPOSAL, PROPOSALCOUNT, - PROPOSALVOTE, REBASE_CLAIMED, VOTERSPROPOSAL, VOTERS_VOTE, + EmissionVaultPool, Proposal, Vote, VotePair, ADMIN, APPCURRENTPROPOSAL, BRIBES_BY_PROPOSAL, + COMPLETEDPROPOSALS, CSWAP_ID, DELEGATED, DELEGATION_INFO, DELEGATION_STATS, EMISSION, + EMISSION_REWARD, PROPOSAL, PROPOSALCOUNT, PROPOSALVOTE, REBASE_CLAIMED, VOTERSPROPOSAL, + VOTERS_CLAIM, VOTERS_CLAIMED_PROPOSALS, VOTERS_VOTE, }; use crate::state::{ LockingPeriod, PeriodWeight, State, Status, TokenInfo, TokenSupply, Vtoken, STATE, SUPPLY, @@ -151,9 +155,8 @@ pub fn execute( ////get ext pairs vec from app let mut ext_pairs = query_extended_pair_by_app(deps.as_ref(), app_id)?; - for val in pools { - ext_pairs.push(val); - } + ext_pairs.append(&mut pools); + raise_proposal(deps, env, info, app_id, ext_pairs) } ExecuteMsg::Bribe { @@ -171,7 +174,10 @@ pub fn execute( bribe_proposal(deps, env, info, proposal_id, extended_pair, bribe_coin) } - ExecuteMsg::ClaimReward { app_id } => claim_rewards(deps, env, info, app_id), + ExecuteMsg::ClaimReward { + app_id, + proposal_id, + } => claim_rewards(deps, env, info, app_id, proposal_id), ExecuteMsg::Emission { proposal_id } => emission(deps, env, info, proposal_id), ExecuteMsg::Lock { app_id, @@ -192,187 +198,63 @@ pub fn execute( val: "Wrong Deposit token".to_string(), }); } + let delegation_info = DELEGATION_INFO.may_load(deps.storage, info.sender.clone())?; + if delegation_info.is_some() { + return Err(ContractError::CustomError { + val: "The delegated address cannot create a lock".to_string(), + }); + } + handle_lock_nft(deps, env, info, app_id, locking_period, recipient) } ExecuteMsg::Withdraw { denom } => handle_withdraw(deps, env, info, denom), - ExecuteMsg::Transfer { - recipient, - locking_period, - denom, - } => handle_transfer(deps, env, info, recipient, locking_period, denom), + // ExecuteMsg::Transfer { + // recipient, + // locking_period, + // denom, + // } => handle_transfer(deps, env, info, recipient, locking_period, denom), ExecuteMsg::Rebase { proposal_id } => calculate_rebase_reward(deps, env, info, proposal_id), ExecuteMsg::Delegate { delegation_address, denom, ratio, } => delegate(deps, env, info, delegation_address, denom, ratio), - ExecuteMsg::Undelegate { - delegation_address, - denom, - } => undelegate(deps, env, info, delegation_address, denom), + ExecuteMsg::Undelegate { delegation_address } => { + undelegate(deps, env, info, delegation_address) + } ExecuteMsg::UpdateProtocolFees { delegate_address, fees, } => update_protocol_fees(deps, env, info, delegate_address, fees), - } -} - -//// delegation function///// - -pub fn delegate( - deps: DepsMut, //indicates how many dependencies have been delegated so far - env: Env, - info: MessageInfo, - delegation_address: Addr, - denom: String, - ratio: Decimal, -) -> Result, ContractError> { - //// check if delegation_address exists//// - let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; - if delegation_info.is_none() { - return Err(ContractError::CustomError { - val: "Delegation info not found".to_string(), - }); - } - - ///// check if sender is not delegated - if info.sender.clone() == delegation_address { - return Err(ContractError::CustomError { - val: "Sender is not allowed to self-delegated".to_string(), - }); - } - - ///// get voting power - //balance of owner for the for denom for voting - - let vtokens = VTOKENS.may_load_at_height( - deps.storage, - (info.sender.clone(), &denom), - env.block.height, - )?; - - if vtokens.is_none() { - return Err(ContractError::CustomError { - val: "No tokens locked to perform voting on proposals".to_string(), - }); - } - - let vtokens = vtokens.unwrap(); - // calculate voting power for the the proposal - let mut vote_power: u128 = 0; - - for vtoken in vtokens { - vote_power += vtoken.vtoken.amount.u128(); - } - - let total_delegated_amount = ratio.mul(Uint128::from(vote_power)).u128(); - let mut delegation = DELEGATED - .may_load(deps.storage, info.sender.clone())? - .unwrap(); - - let mut delegations = delegation.delegations; - let mut prev_delegation: u128 = 0; - let mut found: bool = false; - for delegation_tmp in delegations.iter_mut() { - if delegation_address == delegation_tmp.delegated_to { - prev_delegation = delegation_tmp.delegated; - delegation_tmp.delegated = total_delegated_amount; - delegation_tmp.delegated_at = env.block.time; - delegation_tmp.delegation_end_at = env.block.time.plus_seconds(86400); ///////set as 1 day - found = true; - break; - } else { - continue; - } - } - - if found { - delegation.total_casted = - delegation.total_casted - prev_delegation + total_delegated_amount; - delegation.delegations = delegations; - DELEGATED.save( - deps.storage, - info.sender.clone(), - &delegation, - env.block.height, - )?; - } else { - let delegation_new = Delegation { - delegated_to: delegation_address.clone(), - delegated_at: env.block.time, - delegation_end_at: env.block.time.plus_seconds(86400), ///////set as 1 day - delegated: total_delegated_amount, - }; - delegations.push(delegation_new); - delegation.delegations = delegations; - DELEGATED.save( - deps.storage, - info.sender.clone(), - &delegation, - env.block.height, - )?; - } - - Ok(Response::new() - .add_attribute("action", "delegate") - .add_attribute("from", info.sender) - .add_attribute("delegated_address", delegation_address)) -} - -pub fn undelegate( - deps: DepsMut, - env: Env, - info: MessageInfo, - delegation_address: Addr, - denom: String, -) -> Result, ContractError> { - //// check if delegation_address exists//// - let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; - if delegation_info.is_none() { - return Err(ContractError::CustomError { - val: "Emission calculation did not take place to initiate foundation calculation" - .to_string(), - }); - } + ExecuteMsg::ClaimRewardsDelegated { + delegated_address, + proposal_id, + app_id, + } => claim_rewards_delegated(deps, env, info, delegated_address, proposal_id, app_id), + ExecuteMsg::UpdateExcludedFeePair { + delegate_address, + harbor_app_id, + cswap_app_id, + excluded_fee_pair, + } => update_excluded_fee_pair( + deps, + env, + info, + delegate_address, + harbor_app_id, + cswap_app_id, + excluded_fee_pair, + ), + ExecuteMsg::DelegatedProtocolFeeClaim { + delegated_address, + app_id, + proposal_id, + } => delegated_protocol_fee_claim(deps, env, info, delegated_address, app_id, proposal_id), - ////// check if delegation is present /////// - let delegation = DELEGATED.may_load(deps.storage, info.sender.clone())?; - if delegation.is_none() { - return Err(ContractError::CustomError { - val: "No active delegation present to undelegate".to_string(), - }); + _ => Err(ContractError::CustomError { + val: "Invalid message".to_string(), + }), } - - //// check if UnDelegation time has reached - let mut delegation_info = delegation.unwrap(); - let mut delegations = delegation_info.delegations; - - let delegations_len = delegations.len(); - - for i in 0..delegations_len { - if delegations[i].delegated_to == delegation_address { - if delegations[i].delegation_end_at > env.block.time { - return Err(ContractError::CustomError { - val: "Yet to reach UnDelegation time".to_string(), - }); - } - } else { - delegations.remove(i); - delegation_info.delegations = delegations; - DELEGATED.save( - deps.storage, - info.sender.clone(), - &delegation_info, - env.block.height, - )?; - break; - } - } - - Ok(Response::new() - .add_attribute("action", "undelegate") - .add_attribute("from", info.sender) - .add_attribute("delegated_address", delegation_address)) } pub fn emission_foundation( @@ -685,16 +567,33 @@ pub fn handle_withdraw( vtokens_denom.remove(index); } - let mut delegation = DELEGATED - .may_load(deps.storage, info.sender.clone())? - .unwrap(); - let total_delegated = delegation.total_casted; + let delegation = DELEGATED.may_load(deps.storage, info.sender.clone())?; - if total_delegated < vote_power - vwithdrawable { - } else { + let mut total_delegated = 0u128; + if delegation.is_some() { + let delegation = delegation.clone().unwrap(); + total_delegated = delegation.total_casted; + } + + if total_delegated > vote_power - vwithdrawable { + let mut delegation = delegation.unwrap().clone(); for delegation_temp in delegation.delegations.iter_mut() { let rhs = Decimal::from_ratio(vote_power - vwithdrawable, total_delegated); + let temp = delegation_temp.delegated; + let mut delegation_stats = DELEGATION_STATS + .may_load(deps.storage, delegation_temp.delegated_to.clone())? + .unwrap(); + delegation_stats.total_delegated -= temp; + delegation_stats.total_delegated += + rhs.mul(Uint128::new(delegation_temp.delegated)).u128(); + delegation_temp.delegated = rhs.mul(Uint128::new(delegation_temp.delegated)).u128(); + DELEGATION_STATS.save( + deps.storage, + delegation_temp.delegated_to.clone(), + &delegation_stats, + env.block.height, + )?; } delegation.total_casted = delegation.total_casted - vote_power - vwithdrawable; DELEGATED.save( @@ -876,7 +775,7 @@ pub fn bribe_proposal( return Err(ContractError::InsufficientFunds { funds: 0 }); } else if info.funds.len() > 1 { return Err(ContractError::CustomError { - val: String::from("Multiple denominations are not supported as yet."), + val: String::from("Multiple denominations are not supported"), }); } @@ -907,25 +806,17 @@ pub fn bribe_proposal( } // UPDATE BRIBE FOR PROPOSAL (IF EXISTS THEN UPDATE ELSE APPEND) - let mut existing_bribes = - match BRIBES_BY_PROPOSAL.may_load(deps.storage, (proposal_id, extended_pair))? { - Some(record) => record, - None => vec![], - }; + let mut existing_bribes = BRIBES_BY_PROPOSAL + .may_load(deps.storage, (proposal_id, extended_pair))? + .unwrap_or_default(); - if !existing_bribes.is_empty() { - let mut found = false; - for coin1 in existing_bribes.iter_mut() { - if bribe_coin.denom == coin1.denom { - coin1.amount += bribe_coin.amount; - found = true; - } - } - if !found { - existing_bribes.push(bribe_coin); - } + if let Some(coin) = existing_bribes + .iter_mut() + .find(|c| c.denom == bribe_coin.denom) + { + coin.amount += bribe_coin.amount; } else { - existing_bribes = vec![bribe_coin]; + existing_bribes.push(bribe_coin); } BRIBES_BY_PROPOSAL.save(deps.storage, (proposal_id, extended_pair), &existing_bribes)?; @@ -937,64 +828,82 @@ pub fn claim_rewards( env: Env, info: MessageInfo, app_id: u64, + proposal_id: Option, ) -> Result, ContractError> { if !info.funds.is_empty() { return Err(ContractError::FundsNotAllowed {}); } - //Check active proposal - let max_proposal_claimed = MAXPROPOSALCLAIMED - .load(deps.storage, (app_id, info.sender.clone())) - .unwrap_or_default(); + let delegation_info = DELEGATION_INFO.may_load(deps.storage, info.sender.clone())?; + if delegation_info.is_some() { + return Err(ContractError::CustomError { + val: String::from("Delegated address cannot claim"), + }); + } let all_proposals = match COMPLETEDPROPOSALS.may_load(deps.storage, app_id)? { Some(val) => val, None => vec![], }; + if let Some(proposal_id) = proposal_id { + if !all_proposals.contains(&proposal_id) { + return Err(ContractError::CustomError { + val: String::from("Proposal not completed"), + }); + } - let mut bribe_coins = calculate_bribe_reward( - deps.as_ref(), - env.clone(), - info.clone(), - max_proposal_claimed, - all_proposals.clone(), - app_id, - )?; + let voters_claimed = VOTERS_CLAIM + .load(deps.storage, (info.sender.clone(), proposal_id)) + .unwrap_or_default(); - let surplus_share = calculate_surplus_reward( + if voters_claimed { + return Err(ContractError::CustomError { + val: String::from("Already claimed"), + }); + } + + let mut bribe_coin = + calculate_bribe_reward_proposal(deps.as_ref(), env, info.clone(), proposal_id)?; + VOTERS_CLAIM.save(deps.storage, (info.sender.clone(), proposal_id), &true)?; + let mut claimed_proposal = + match VOTERS_CLAIMED_PROPOSALS.may_load(deps.storage, info.sender.clone())? { + Some(val) => val, + None => vec![], + }; + claimed_proposal.push(proposal_id); + claimed_proposal.sort(); + VOTERS_CLAIMED_PROPOSALS.save(deps.storage, info.sender.clone(), &claimed_proposal)?; + bribe_coin.sort_by_key(|element| element.denom.clone()); + + return Ok(Response::new() + .add_attribute("method", "External Incentive Claimed") + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: bribe_coin, + })); + } + + let (mut bribe_coins, claimed_proposals) = calculate_bribe_reward( deps.as_ref(), env, info.clone(), - max_proposal_claimed, all_proposals.clone(), app_id, )?; - if !bribe_coins.is_empty() { - if !surplus_share.amount.is_zero() { - for coin1 in bribe_coins.iter_mut() { - if surplus_share.denom == coin1.denom { - coin1.amount += surplus_share.amount; - } - } - } - } else if !surplus_share.amount.is_zero() { - bribe_coins = vec![surplus_share] - } else { - bribe_coins = vec![] + VOTERS_CLAIMED_PROPOSALS.save(deps.storage, info.sender.clone(), &claimed_proposals)?; + for proposal in claimed_proposals { + VOTERS_CLAIM.save(deps.storage, (info.sender.clone(), proposal), &true)?; } - MAXPROPOSALCLAIMED.save( - deps.storage, - (app_id, info.sender.clone()), - all_proposals.last().unwrap(), - )?; - - bribe_coins.sort_by_key(|element| element.denom.clone()); - if !bribe_coins.is_empty() { + bribe_coins.sort_by_key(|element| element.denom.clone()); + + // remove all coin having amount as 0 + bribe_coins.retain(|coin| !coin.amount.is_zero()); + Ok(Response::new() - .add_attribute("method", "Bribe Claimed") + .add_attribute("method", "External Incentive Claimed") .add_message(BankMsg::Send { to_address: info.sender.to_string(), amount: bribe_coins, @@ -1010,13 +919,17 @@ pub fn calculate_bribe_reward( deps: Deps, _env: Env, info: MessageInfo, - max_proposal_claimed: u64, all_proposals: Vec, _app_id: u64, -) -> Result, ContractError> { +) -> Result<(Vec, Vec), ContractError> { let mut bribe_coins: Vec = vec![]; + let mut claimed_proposal = + match VOTERS_CLAIMED_PROPOSALS.may_load(deps.storage, info.sender.clone())? { + Some(val) => val, + None => vec![], + }; for proposalid in all_proposals { - if proposalid <= max_proposal_claimed { + if claimed_proposal.contains(&proposalid) { continue; } let vote = match VOTERSPROPOSAL.may_load(deps.storage, (info.sender.clone(), proposalid))? { @@ -1036,37 +949,82 @@ pub fn calculate_bribe_reward( None => vec![], }; - let mut claimable_bribe: Vec = vec![]; - for coin in total_bribe.clone() { - let claimable_amount = (Decimal::new(Uint128::from(pair.vote_weight)) - .div(Decimal::new(Uint128::from(total_vote_weight)))) - .mul(coin.amount); - let claimable_coin = Coin { - amount: claimable_amount, - denom: coin.denom, - }; - claimable_bribe.push(claimable_coin); - } + let claimable_bribe: Vec = total_bribe + .iter() + .map(|coin| { + let claimable_amount = (Decimal::new(Uint128::from(pair.vote_weight)) + .div(Decimal::new(Uint128::from(total_vote_weight)))) + .mul(coin.amount); + Coin { + amount: claimable_amount, + denom: coin.denom.clone(), + } + }) + .collect(); - for bribe_deposited in claimable_bribe.clone() { - match bribe_coins + for bribe_deposited in claimable_bribe { + if let Some(pivot) = bribe_coins .iter_mut() .find(|p| bribe_deposited.denom == p.denom) { - Some(pivot) => { - pivot.denom = bribe_deposited.denom; - pivot.amount += bribe_deposited.amount; - } - None => { - bribe_coins.push(bribe_deposited); - } + pivot.amount += bribe_deposited.amount; + } else { + bribe_coins.push(bribe_deposited); } } } + claimed_proposal.push(proposalid); + claimed_proposal.sort(); } //// send bank message to band + Ok((bribe_coins, claimed_proposal)) +} + +pub fn calculate_bribe_reward_proposal( + deps: Deps, + _env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result, ContractError> { + let mut bribe_coins: Vec = vec![]; + + let vote = VOTERSPROPOSAL.load(deps.storage, (info.sender, proposal_id))?; + + for pair in vote.votes { + let total_vote_weight = PROPOSALVOTE + .load(deps.storage, (proposal_id, pair.extended_pair))? + .u128(); + + let total_bribe = BRIBES_BY_PROPOSAL + .may_load(deps.storage, (proposal_id, pair.extended_pair))? + .unwrap_or_default(); + + let claimable_bribe: Vec = total_bribe + .iter() + .map(|coin| { + let claimable_amount = (Decimal::new(Uint128::from(pair.vote_weight)) + .div(Decimal::new(Uint128::from(total_vote_weight)))) + .mul(coin.amount); + Coin { + amount: claimable_amount, + denom: coin.denom.clone(), + } + }) + .collect(); + + for bribe_deposited in claimable_bribe { + if let Some(pivot) = bribe_coins + .iter_mut() + .find(|p| bribe_deposited.denom == p.denom) + { + pivot.amount += bribe_deposited.amount; + } else { + bribe_coins.push(bribe_deposited); + } + } + } Ok(bribe_coins) } @@ -1454,7 +1412,7 @@ pub fn update_protocol_fees( env: Env, info: MessageInfo, delegation_address: Addr, - new_delegator_fees: Decimal, + new_protocol_fees: Decimal, ) -> Result, ContractError> { //// check if delegation_address exists//// let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; @@ -1470,18 +1428,14 @@ pub fn update_protocol_fees( val: "Sender is not the owner of the delegation".to_string(), }); } - if new_delegator_fees > delegation_info.delegator_fees { - return Err(ContractError::CustomError { - val: "New Delegator fees cannot be greater than delegator fees".to_string(), - }); - } - if new_delegator_fees < Decimal::zero() { + if new_protocol_fees > delegation_info.delegator_fees || new_protocol_fees < Decimal::zero() { return Err(ContractError::CustomError { - val: "New Delegator fees percentage cannot be less than 0 %".to_string(), + val: "New Delegator fees cannot be greater than delegator fees nor can be less than 0%" + .to_string(), }); } // update the protocol fees - delegation_info.delegator_fees = new_delegator_fees; + delegation_info.protocol_fees = new_protocol_fees; DELEGATION_INFO.save( deps.storage, delegation_address, @@ -1494,11 +1448,21 @@ pub fn update_protocol_fees( .add_attribute("from", info.sender)) } +fn has_duplicate_elements(vec: &Vec) -> bool { + let mut seen_elements = std::collections::HashSet::new(); + for element in vec.iter() { + if seen_elements.contains(element) { + return true; + } + seen_elements.insert(element); + } + return false; +} pub fn vote_proposal( deps: DepsMut, env: Env, info: MessageInfo, - app_id: u64, + _app_id: u64, proposal_id: u64, extended_pair: Vec, gov_token_denom: String, @@ -1510,6 +1474,7 @@ pub fn vote_proposal( val: "Admin cannot vote".to_string(), }); } + // do not accept funds if !info.funds.is_empty() { return Err(ContractError::FundsNotAllowed {}); @@ -1526,7 +1491,7 @@ pub fn vote_proposal( } // check if vote sequence is correct - if extended_pair.len() != ratio.clone().len() { + if extended_pair.len() != ratio.len() { return Err(ContractError::CustomError { val: "Invalid ratio".to_string(), }); @@ -1534,7 +1499,7 @@ pub fn vote_proposal( let mut total_ration = Decimal::zero(); for ratio in ratio.iter() { - total_ration = total_ration + ratio; + total_ration += ratio; } //// check if total ratio is 100% @@ -1544,6 +1509,12 @@ pub fn vote_proposal( }); } + if has_duplicate_elements(&extended_pair) { + return Err(ContractError::CustomError { + val: "Extended pair has duplicate elements".to_string(), + }); + } + // check if total ratio is not 0% if total_ration == Decimal::zero() { return Err(ContractError::CustomError { @@ -1562,16 +1533,20 @@ pub fn vote_proposal( //// check if extended pair exists in proposal's extended pair - if extended_pairs_proposal + if !extended_pair .iter() - .all(|item| extended_pair.contains(item)) + .all(|item| extended_pairs_proposal.contains(item)) { return Err(ContractError::CustomError { val: "Extended pair does not exist in proposal".to_string(), }); } - //balance of owner for the for denom for voting + // check if extended pair has no duplicate + + //balance of denom for voting + + let mut vote_power: u128 = 0; let vtokens = VTOKENS.may_load_at_height( deps.storage, @@ -1579,23 +1554,34 @@ pub fn vote_proposal( proposal.height, )?; - if vtokens.is_none() { + let delegator_locked = + DELEGATION_STATS.may_load_at_height(deps.storage, info.sender.clone(), proposal.height)?; + + if vtokens.is_none() && delegator_locked.is_none() { return Err(ContractError::CustomError { val: "No tokens locked to perform voting on proposals".to_string(), }); } + if let Some(_vtokens) = vtokens { + // calculate voting power for the proposal + for vtoken in _vtokens { + vote_power += vtoken.vtoken.amount.u128(); + } + } - let vtokens = vtokens.unwrap(); - - // calculate voting power for the the proposal - let mut vote_power: u128 = 0; + if let Some(delegator_locked) = delegator_locked { + vote_power = delegator_locked.total_delegated; + } - for vtoken in vtokens { - vote_power += vtoken.vtoken.amount.u128(); + if vote_power == 0 { + return Err(ContractError::CustomError { + val: "No tokens locked to perform voting on proposals".to_string(), + }); } //// decrease voting power if delegated - let delegation = DELEGATED.may_load(deps.storage, info.sender.clone())?; + let delegation = + DELEGATED.may_load_at_height(deps.storage, info.sender.clone(), proposal.height)?; if delegation.is_some() { let delegation = delegation.unwrap(); vote_power -= delegation.total_casted; @@ -1639,7 +1625,7 @@ pub fn vote_proposal( (proposal_id, pair_vote.extended_pair), &proposal_vote, )?; - proposal.total_voted_weight -= pair_vote.vote_weight; + proposal.total_voted_weight += pair_vote.vote_weight; } let vote = Vote { voting_power_total: vote_power, @@ -1675,7 +1661,9 @@ pub fn raise_proposal( }); } //check no proposal active for app - let current_app_proposal = (APPCURRENTPROPOSAL.may_load(deps.storage, app_id)?).unwrap_or(0); + let current_app_proposal = APPCURRENTPROPOSAL + .may_load(deps.storage, app_id)? + .unwrap_or(0); // if proposal already exist , check if whether it is in voting period // proposal cannot be raised until current proposal voting time is ended @@ -1809,18 +1797,24 @@ pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result Decimal::one() { + if delegation_info.protocol_fees < Decimal::zero() + || delegation_info.protocol_fees > Decimal::one() + { return Err(ContractError::CustomError { - val: "Protocol fees percentage cannot be greater than 100 %".to_string(), + val: "Protocol fees percentage cannot be less than 0 % or greater than 100%" + .to_string(), }); } - if delegation_info.protocol_fees < Decimal::zero() { + if delegation_info.delegator_fees < Decimal::zero() + || delegation_info.delegator_fees > Decimal::one() + { return Err(ContractError::CustomError { - val: "Protocol fees percentage cannot be less than 0 %".to_string(), + val: "Delegator fees percentage cannot be less than 0 % or greater than 100%" + .to_string(), }); } - if delegation_info.delegated_address == delegation_info.fee_collector_adress { + if delegation_info.delegated_address == delegation_info.fee_collector_address { return Err(ContractError::CustomError { val: "Delegator and fee collector address cannot be same".to_string(), }); @@ -1845,18 +1839,24 @@ pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result Decimal::one() { + if delegation_info.protocol_fees < Decimal::zero() + || delegation_info.protocol_fees > Decimal::one() + { return Err(ContractError::CustomError { - val: "Protocol fees percentage cannot be greater than 100 %".to_string(), + val: "Protocol fees percentage cannot be less than 0 % or greater than 100%" + .to_string(), }); } - if delegation_info.protocol_fees < Decimal::zero() { + if delegation_info.delegator_fees < Decimal::zero() + || delegation_info.delegator_fees > Decimal::one() + { return Err(ContractError::CustomError { - val: "Protocol fees percentage cannot be less than 0 %".to_string(), + val: "Delegator fees percentage cannot be less than 0 % or greater than 100%" + .to_string(), }); } - if delegation_info.delegated_address == delegation_info.fee_collector_adress { + if delegation_info.delegated_address == delegation_info.fee_collector_address { return Err(ContractError::CustomError { val: "Delegator and fee collector address cannot be same".to_string(), }); diff --git a/src/delegated.rs b/src/delegated.rs new file mode 100644 index 0000000..12b9802 --- /dev/null +++ b/src/delegated.rs @@ -0,0 +1,597 @@ +use crate::contract::calculate_bribe_reward_proposal; +use crate::error::ContractError; +use crate::helpers::{query_app_exists, query_extended_pair_by_app, query_pool_by_app}; +use crate::state::{ + Delegation, DelegationStats, UserDelegationInfo, BRIBES_BY_PROPOSAL, COMPLETEDPROPOSALS, + DELEGATED, DELEGATION_INFO, DELEGATION_STATS, DELEGATOR_CLAIM, DELEGATOR_CLAIMED_PROPOSALS, + PROPOSAL, PROPOSALVOTE, VOTERSPROPOSAL, VOTERS_CLAIM, VOTERS_CLAIMED_PROPOSALS, VTOKENS, +}; + +use comdex_bindings::{ComdexMessages, ComdexQuery}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::{ + Addr, BankMsg, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Response, Uint128, +}; +use std::ops::{Div, Mul}; + +//// delegation function///// + +pub fn delegate( + deps: DepsMut, + env: Env, + info: MessageInfo, + delegation_address: Addr, + denom: String, + ratio: Decimal, +) -> Result, ContractError> { + if !info.funds.is_empty() { + return Err(ContractError::FundsNotAllowed {}); + } + //// check if delegation_address exists//// + let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; + if delegation_info.is_none() { + return Err(ContractError::CustomError { + val: "Delegation info not found".to_string(), + }); + } + + ///// check if sender is not delegated + if info.sender == delegation_address { + return Err(ContractError::CustomError { + val: "Sender is not allowed to self-delegated".to_string(), + }); + } + + ///// get voting power + // balance of owner for the for denom for voting + + let vtokens = VTOKENS.may_load_at_height( + deps.storage, + (info.sender.clone(), &denom), + env.block.height, + )?; + + let vtokens = vtokens.ok_or_else(|| ContractError::CustomError { + val: "No tokens locked to perform voting on proposals".to_string(), + })?; + + // calculate voting power for the proposal + let vote_power: u128 = vtokens + .iter() + .map(|vtoken| vtoken.vtoken.amount.u128()) + .sum(); + + let mut delegation_stats = + DELEGATION_STATS.may_load(deps.storage, delegation_address.clone())?; + if delegation_stats.is_none() { + delegation_stats = Some(DelegationStats { + total_delegated: 0, + total_delegators: 0, + }) + } + let total_delegated_amount = ratio.mul(Uint128::from(vote_power)).u128(); + let mut delegation = DELEGATED.may_load(deps.storage, info.sender.clone())?; + if delegation.is_none() { + delegation = Some(UserDelegationInfo { + total_casted: 0, + delegations: vec![], + }) + } + let mut delegation = delegation.unwrap(); + let mut delegations = delegation.delegations; + + if let Some(delegation_index) = delegations + .iter() + .position(|d| d.delegated_to == delegation_address) + { + let prev_delegation = delegations[delegation_index].delegated; + delegations[delegation_index] = Delegation { + delegated_to: delegation_address.clone(), + delegated_at: env.block.time, + delegation_end_at: env.block.time.plus_seconds(86400), + delegated: total_delegated_amount, + }; + + let mut delegation_stats = delegation_stats.unwrap(); + delegation_stats.total_delegated -= prev_delegation; + delegation_stats.total_delegated += total_delegated_amount; + DELEGATION_STATS.save( + deps.storage, + delegation_address.clone(), + &delegation_stats, + env.block.height, + )?; + + delegation.total_casted = + delegation.total_casted - prev_delegation + total_delegated_amount; + } else { + let delegation_new = Delegation { + delegated_to: delegation_address.clone(), + delegated_at: env.block.time, + delegation_end_at: env.block.time.plus_seconds(86400), + delegated: total_delegated_amount, + }; + delegations.push(delegation_new); + + let mut delegation_stats = delegation_stats.unwrap(); + delegation_stats.total_delegated += total_delegated_amount; + delegation_stats.total_delegators += 1; + DELEGATION_STATS.save( + deps.storage, + delegation_address.clone(), + &delegation_stats, + env.block.height, + )?; + delegation.total_casted += total_delegated_amount; + } + + delegation.delegations = delegations; + DELEGATED.save( + deps.storage, + info.sender.clone(), + &delegation, + env.block.height, + )?; + + Ok(Response::new() + .add_attribute("action", "delegate") + .add_attribute("from", info.sender) + .add_attribute("delegated_address", delegation_address)) +} + +pub fn undelegate( + deps: DepsMut, + env: Env, + info: MessageInfo, + delegation_address: Addr, +) -> Result, ContractError> { + if !info.funds.is_empty() { + return Err(ContractError::FundsNotAllowed {}); + } + //// check if delegation_address exists//// + let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; + if delegation_info.is_none() { + return Err(ContractError::CustomError { + val: "Delegation info not found".to_string(), + }); + } + + let mut delegation_stats = DELEGATION_STATS.load(deps.storage, delegation_address.clone())?; + + ////// check if delegation is present /////// + let mut delegation = DELEGATED.load(deps.storage, info.sender.clone())?; + + let mut delegations = delegation.delegations; + let delegation_index = delegations + .iter() + .position(|d| d.delegated_to == delegation_address); + if delegation_index.is_none() { + return Err(ContractError::CustomError { + val: "No active delegation present to undelegate".to_string(), + }); + } + let delegation_index = delegation_index.unwrap(); + if delegations[delegation_index].delegation_end_at > env.block.time { + return Err(ContractError::CustomError { + val: "Yet to reach UnDelegation time".to_string(), + }); + } + delegation_stats.total_delegated -= delegations[delegation_index].delegated; + delegation_stats.total_delegators -= 1; + delegation.total_casted -= delegations[delegation_index].delegated; + + delegations.swap_remove(delegation_index); + delegation.delegations = delegations; + + DELEGATED.save( + deps.storage, + info.sender.clone(), + &delegation, + env.block.height, + )?; + DELEGATION_STATS.save( + deps.storage, + delegation_address.clone(), + &delegation_stats, + env.block.height, + )?; + + Ok(Response::new() + .add_attribute("action", "undelegate") + .add_attribute("from", info.sender) + .add_attribute("delegated_address", delegation_address)) +} + +///// +pub fn claim_rewards_delegated( + deps: DepsMut, + env: Env, + info: MessageInfo, + delegated_address: Addr, + proposal_id: Option, + app_id: u64, +) -> Result, ContractError> { + ///// get delegated fees ///// + let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegated_address.clone())?; + if delegation_info.is_none() { + return Err(ContractError::CustomError { + val: "Invalid Delegated Address".to_string(), + }); + } + + if !info.funds.is_empty() { + return Err(ContractError::FundsNotAllowed {}); + } + let all_proposals = match COMPLETEDPROPOSALS.may_load(deps.storage, app_id)? { + Some(val) => val, + None => vec![], + }; + let mut bribe_coins = vec![]; + let mut fee_coin = vec![]; + let delegation_info = delegation_info.unwrap(); + + if let Some(proposal_id) = proposal_id { + let delegator_claimed = DELEGATOR_CLAIM + .load(deps.storage, (info.sender.clone(), proposal_id)) + .unwrap_or_default(); + if delegator_claimed { + return Err(ContractError::CustomError { + val: "Already Claimed".to_string(), + }); + } + let (user_coin, delegated_coin) = calculate_bribe_reward_proposal_delegated( + deps.as_ref(), + env, + info.clone(), + proposal_id, + delegated_address, + )?; + bribe_coins = user_coin; + fee_coin = delegated_coin; + + DELEGATOR_CLAIM.save(deps.storage, (info.sender.clone(), proposal_id), &true)?; + let mut claimed_proposal = + match DELEGATOR_CLAIMED_PROPOSALS.may_load(deps.storage, info.sender.clone())? { + Some(val) => val, + None => vec![], + }; + claimed_proposal.push(proposal_id); + claimed_proposal.sort(); + DELEGATOR_CLAIMED_PROPOSALS.save(deps.storage, info.sender.clone(), &claimed_proposal)?; + bribe_coins.sort_by_key(|element| element.denom.clone()); + fee_coin.sort_by_key(|element| element.denom.clone()); + } else { + let mut fee_coin: Vec = vec![]; + + let mut claimed_proposal = + match DELEGATOR_CLAIMED_PROPOSALS.may_load(deps.storage, info.sender.clone())? { + Some(val) => val, + None => vec![], + }; + + for proposal_id in all_proposals { + let delegator_claimed = DELEGATOR_CLAIM + .load(deps.storage, (info.sender.clone(), proposal_id)) + .unwrap_or_default(); + if delegator_claimed { + continue; + } + + let (user_coin, delegated_coins) = calculate_bribe_reward_proposal_delegated( + deps.as_ref(), + env.clone(), + info.clone(), + proposal_id, + delegated_address.clone(), + )?; + let mut user_coin = user_coin; + let mut delegated_coin = delegated_coins; + bribe_coins.append(&mut user_coin); + fee_coin.append(&mut delegated_coin); + + DELEGATOR_CLAIM.save(deps.storage, (info.sender.clone(), proposal_id), &true)?; + + claimed_proposal.push(proposal_id); + claimed_proposal.sort(); + } + DELEGATOR_CLAIMED_PROPOSALS.save(deps.storage, info.sender.clone(), &claimed_proposal)?; + bribe_coins.sort_by_key(|element| element.denom.clone()); + fee_coin.sort_by_key(|element| element.denom.clone()); + } + if !bribe_coins.is_empty() { + if !fee_coin.is_empty() { + Ok(Response::new() + .add_attribute("method", "External Incentive Claimed") + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: bribe_coins, + }) + .add_message(BankMsg::Send { + to_address: delegation_info.fee_collector_address.to_string(), + amount: fee_coin, + })) + } else { + Ok(Response::new() + .add_attribute("method", "External Incentive Claimed") + .add_message(BankMsg::Send { + to_address: info.sender.to_string(), + amount: bribe_coins, + })) + } + } else if !fee_coin.is_empty() { + Ok(Response::new() + .add_attribute("method", "External Incentive Claimed") + .add_message(BankMsg::Send { + to_address: delegation_info.fee_collector_address.to_string(), + amount: fee_coin, + })) + } else { + Err(ContractError::CustomError { + val: String::from("No rewards to claim."), + }) + } +} + +pub fn calculate_bribe_reward_proposal_delegated( + deps: Deps, + _env: Env, + info: MessageInfo, + proposal_id: u64, + delegated_address: Addr, +) -> Result<(Vec, Vec), ContractError> { + let proposal = PROPOSAL.may_load(deps.storage, proposal_id)?; + if proposal.is_none() { + return Err(ContractError::CustomError { + val: String::from("Proposal does not exist."), + }); + } + let proposal = proposal.unwrap(); + + let delegation_info = DELEGATION_INFO + .may_load_at_height(deps.storage, delegated_address.clone(), proposal.height)? + .unwrap(); + + let mut bribe_coins: Vec = vec![]; + + let vote = VOTERSPROPOSAL.may_load(deps.storage, (delegated_address.clone(), proposal_id))?; + + if let Some(vote) = vote { + for pair in vote.votes { + let total_vote_weight = PROPOSALVOTE + .load(deps.storage, (proposal_id, pair.extended_pair))? + .u128(); + + let total_bribe = BRIBES_BY_PROPOSAL + .may_load(deps.storage, (proposal_id, pair.extended_pair))? + .unwrap_or_else(|| vec![]); + + let claimable_bribe: Vec = total_bribe + .iter() + .map(|coin| { + let claimable_amount = if !delegation_info + .excluded_fee_pair + .contains(&pair.extended_pair) + { + (Decimal::new(Uint128::from(pair.vote_weight)) + .div(Decimal::new(Uint128::from(total_vote_weight))) + .mul(Decimal::one() - delegation_info.protocol_fees)) + .mul(coin.amount) + } else { + (Decimal::new(Uint128::from(pair.vote_weight)) + .div(Decimal::new(Uint128::from(total_vote_weight)))) + .mul(coin.amount) + }; + Coin { + amount: claimable_amount, + denom: coin.denom.clone(), + } + }) + .collect(); + + for bribe_deposited in claimable_bribe { + match bribe_coins + .iter_mut() + .find(|p| bribe_deposited.denom == p.denom) + { + Some(pivot) => { + pivot.denom = bribe_deposited.denom; + pivot.amount += bribe_deposited.amount; + } + None => { + bribe_coins.push(bribe_deposited); + } + } + } + } + } + + let total_bribe_coins = bribe_coins.clone(); + let delegation_user = + DELEGATED.may_load_at_height(deps.storage, info.sender, proposal.height)?; + if delegation_user.is_none() { + return Err(ContractError::CustomError { + val: String::from("delegation does not exist"), + }); + } + let delegation_user = delegation_user.unwrap(); + let delegation = delegation_user + .delegations + .into_iter() + .find(|x| x.delegated_to == delegated_address) + .unwrap(); + + let delegation_stats = DELEGATION_STATS + .may_load_at_height(deps.storage, delegated_address, proposal.height)? + .unwrap(); + + let (user_coin, delegated_coin): (Vec, Vec) = total_bribe_coins + .into_iter() + .map(|coin| { + let amount = coin.amount; + let user_share = amount.mul(Decimal::from_ratio( + delegation.delegated, + delegation_stats.total_delegated, + )); + let delegated_fee = user_share.mul(delegation_info.delegator_fees); + let user_share = user_share - delegated_fee; + let user_share_coin = Coin { + amount: user_share, + denom: coin.denom.clone(), + }; + let delegated_share_coin = Coin { + amount: delegated_fee, + denom: coin.denom, + }; + (user_share_coin, delegated_share_coin) + }) + .unzip(); + + Ok((user_coin, delegated_coin)) +} + +// update excluded_fee_pair +pub fn update_excluded_fee_pair( + deps: DepsMut, + env: Env, + info: MessageInfo, + delegation_address: Addr, + harbor_app_id: u64, + cswap_app_id: u64, + excluded_fee_pair: Vec, +) -> Result, ContractError> { + if !info.funds.is_empty() { + return Err(ContractError::FundsNotAllowed {}); + } + //// check if delegation_address exists//// + let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegation_address.clone())?; + if delegation_info.is_none() { + return Err(ContractError::CustomError { + val: "Delegation address does not exist".to_string(), + }); + } + // check if the sender is the owner of the delegation + let mut delegation_info = delegation_info.unwrap(); + if delegation_info.delegated_address != info.sender { + return Err(ContractError::CustomError { + val: "Sender is not the owner of the delegation".to_string(), + }); + } + //check if app exist + query_app_exists(deps.as_ref(), harbor_app_id)?; + + //check if app exist + query_app_exists(deps.as_ref(), cswap_app_id)?; + + //get ext pairs vec from app + let ext_pairs = query_extended_pair_by_app(deps.as_ref(), harbor_app_id)?; + + //get pools vec from app + let mut pools = query_pool_by_app(deps.as_ref(), cswap_app_id)?; + for pool in pools.iter_mut() { + *pool *= 1000000; + } + + for pair in &excluded_fee_pair { + if !delegation_info.excluded_fee_pair.contains(pair) + && ext_pairs.contains(pair) + && pools.contains(pair) + { + delegation_info.excluded_fee_pair.push(*pair); + } + } + DELEGATION_INFO.save( + deps.storage, + delegation_address, + &delegation_info, + env.block.height, + )?; + Ok(Response::new() + .add_attribute("method", "update_excluded_fee_pair") + .add_attribute( + "excluded_fee_pair", + format!("{:?}", delegation_info.excluded_fee_pair), + )) +} + +pub fn delegated_protocol_fee_claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + delegated_address: Addr, + app_id: u64, + proposal_id: u64, +) -> Result, ContractError> { + if !info.funds.is_empty() { + return Err(ContractError::FundsNotAllowed {}); + } + + if info.sender != delegated_address { + return Err(ContractError::CustomError { + val: "Sender is not a delegated address".to_string(), + }); + } + + let proposal = PROPOSAL.load(deps.storage, proposal_id)?; + + let delegation_info = DELEGATION_INFO.may_load_at_height( + deps.storage, + delegated_address.clone(), + proposal.height, + )?; + + if delegation_info.is_none() { + return Err(ContractError::CustomError { + val: "Delegation info not found".to_string(), + }); + } + + let delegation_info = delegation_info.unwrap(); + + let all_proposals = match COMPLETEDPROPOSALS.may_load(deps.storage, app_id)? { + Some(val) => val, + None => vec![], + }; + + if !all_proposals.contains(&proposal_id) { + return Err(ContractError::CustomError { + val: "Proposal not completed".to_string(), + }); + } + + let voters_claimed = VOTERS_CLAIM + .load(deps.storage, (info.sender.clone(), proposal_id)) + .unwrap_or_default(); + + if voters_claimed { + return Err(ContractError::CustomError { + val: "Voter already claimed".to_string(), + }); + } + + let mut _bribe_coin = + calculate_bribe_reward_proposal(deps.as_ref(), env, info.clone(), proposal_id)?; + + let mut claimed_proposal = + match VOTERS_CLAIMED_PROPOSALS.may_load(deps.storage, info.sender.clone())? { + Some(val) => val, + None => vec![], + }; + + claimed_proposal.push(proposal_id); + claimed_proposal.sort(); + _bribe_coin.sort_by_key(|element| element.denom.clone()); + + for coin in _bribe_coin.iter_mut() { + coin.amount = coin.amount * delegation_info.protocol_fees; + } + VOTERS_CLAIMED_PROPOSALS.save(deps.storage, info.sender.clone(), &claimed_proposal)?; + VOTERS_CLAIM.save(deps.storage, (info.sender.clone(), proposal_id), &true)?; + + Ok(Response::new() + .add_message(BankMsg::Send { + to_address: delegation_info.fee_collector_address.to_string(), + amount: _bribe_coin, + }) + .add_attribute("action", "Withdraw protocol fees ") + .add_attribute("from", info.sender)) +} diff --git a/src/lib.rs b/src/lib.rs index b5cfa8f..8b0253e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,6 @@ pub mod helpers; pub mod msg; pub mod query; pub mod state; -pub mod test; +//pub mod test; pub use crate::error::ContractError; +pub mod delegated; diff --git a/src/msg.rs b/src/msg.rs index 1d8d616..3b42adb 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -34,6 +34,7 @@ pub enum ExecuteMsg { }, ClaimReward { app_id: u64, + proposal_id: Option, }, Bribe { proposal_id: u64, @@ -65,12 +66,27 @@ pub enum ExecuteMsg { }, Undelegate { delegation_address: Addr, - denom: String, }, UpdateProtocolFees { delegate_address: Addr, fees: Decimal, }, + ClaimRewardsDelegated { + delegated_address: Addr, + proposal_id: Option, + app_id: u64, + }, + UpdateExcludedFeePair { + delegate_address: Addr, + harbor_app_id: u64, + cswap_app_id: u64, + excluded_fee_pair: Vec, + }, + DelegatedProtocolFeeClaim { + delegated_address: Addr, + app_id: u64, + proposal_id: u64, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] @@ -156,6 +172,36 @@ pub enum QueryMsg { gov_token_denom: String, gov_token_id: u64, }, + DelegationRequest { + delegated_address: Addr, + delegator_address: Addr, + height: Option, + }, + CurrentProposalUser { + app_id: u64, + address: Addr, + }, + DelegatorParamRequest { + delegated_address: Addr, + }, + GetEmissionVotingPower { + address: Addr, + proposal_id: u64, + denom: String, + }, + DelegationStats { + delegated_address: Addr, + height: Option, + }, + UserDelegationStats { + delegator_address: Addr, + height: Option, + }, + UserEmissionVoting { + address: Addr, + proposal_id: u64, + denom: String, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] @@ -206,32 +252,32 @@ pub struct RebaseResponse { pub rebase_amount: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct WithdrawableResponse { pub amount: Coin, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct UnlockedTokensResponse { pub tokens: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct LockedTokensResponse { pub tokens: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct IssuedVtokensResponse { pub vtokens: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct ProposalVoteRespons { pub proposal_pair_data: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct ProposalPairVote { pub extended_pair_id: u64, pub my_vote: Uint128, diff --git a/src/query.rs b/src/query.rs index 9786b2f..52a8c9b 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,18 +1,15 @@ -use std::borrow::Borrow; - use crate::error::ContractError; use crate::helpers::get_token_supply; -use crate::msg::{ - IssuedNftResponse, QueryMsg, RebaseResponse, - WithdrawableResponse, -}; +use crate::msg::{IssuedNftResponse, QueryMsg, WithdrawableResponse}; use crate::state::{ - Emission, EmissionVaultPool, LockingPeriod, Proposal, State, TokenSupply, Vote, Vtoken, ADMIN, - APPCURRENTPROPOSAL, BRIBES_BY_PROPOSAL, COMPLETEDPROPOSALS, EMISSION, EMISSION_REWARD, - MAXPROPOSALCLAIMED, PROPOSAL, PROPOSALVOTE, REBASE_CLAIMED, STATE, SUPPLY, TOKENS, - VOTERSPROPOSAL, VOTERS_VOTE, VTOKENS, + Delegation, DelegationInfo, DelegationStats, Emission, EmissionVaultPool, LockingPeriod, + Proposal, RebaseAllResponse, RewardAllResponse, State, TokenSupply, UserDelegationInfo, Vote, + VoteResponse, Vtoken, ADMIN, APPCURRENTPROPOSAL, BRIBES_BY_PROPOSAL, COMPLETEDPROPOSALS, + DELEGATED, DELEGATION_INFO, DELEGATION_STATS, EMISSION, EMISSION_REWARD, PROPOSAL, + PROPOSALVOTE, REBASE_CLAIMED, STATE, SUPPLY, TOKENS, VOTERSPROPOSAL, VOTERS_CLAIM, VOTERS_VOTE, + VTOKENS, }; -use comdex_bindings::ComdexQuery; +use comdex_bindings::{ComdexQuery, GetPoolByAppResponse}; use cosmwasm_std::{ entry_point, to_binary, Addr, Binary, Coin, Decimal, Deps, Env, QueryRequest, StdError, StdResult, Uint128, WasmQuery, @@ -103,11 +100,69 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + to_binary(&query_current_proposal_user(deps, env, address, app_id)?) + } + QueryMsg::DelegationRequest { + delegated_address, + delegator_address, + height, + } => to_binary(&query_delegation( + deps, + env, + delegated_address, + delegator_address, + height, + )?), + QueryMsg::DelegatorParamRequest { delegated_address } => { + to_binary(&query_delegator_param(deps, env, delegated_address)?) + } + QueryMsg::DelegationStats { + delegated_address, + height, + } => to_binary(&query_delegated_stats( + deps, + env, + delegated_address, + height, + )?), + QueryMsg::UserDelegationStats { + delegator_address, + height, + } => to_binary(&query_user_delegation_all( + deps, + env, + delegator_address, + height, + )?), + QueryMsg::UserEmissionVoting { + address, + proposal_id, + denom, + } => to_binary(&query_emission_voting_power( + deps, + env, + address, + proposal_id, + denom, + )?), _ => panic!("Not implemented"), } } +pub fn query_pool_by_app( + deps: Deps, + app_mapping_id_param: u64, +) -> StdResult> { + let pool_pair = deps + .querier + .query::(&QueryRequest::Custom(ComdexQuery::GetPoolByApp { + app_id: app_mapping_id_param, + }))?; + + Ok(pool_pair.pools) +} + pub fn query_admin(deps: Deps, _env: Env) -> StdResult> { let admin = ADMIN.get(deps)?; Ok(admin) @@ -365,46 +420,35 @@ pub fn query_bribe_eligible( env: Env, address: Addr, app_id: u64, -) -> StdResult> { - let max_proposal_claimed = MAXPROPOSALCLAIMED - .load(deps.storage, (app_id, address.clone())) - .unwrap_or_default(); - +) -> StdResult> { let all_proposals = match COMPLETEDPROPOSALS.may_load(deps.storage, app_id)? { Some(val) => val, None => vec![], }; - let bribe_coins = calculate_bribe_reward_query( - deps, - env, - max_proposal_claimed, - all_proposals, - address.borrow(), - app_id, - ); - Ok(bribe_coins.unwrap_or_default()) + let bribe_coins = + calculate_bribe_reward_query(deps, env, all_proposals, address.clone(), app_id).unwrap(); + Ok(bribe_coins) } pub fn calculate_bribe_reward_query( deps: Deps, _env: Env, - max_proposal_claimed: u64, all_proposals: Vec, - address: &Addr, + address: Addr, _app_id: u64, -) -> Result, ContractError> { +) -> Result, ContractError> { + let mut resp: Vec = vec![]; //check if active proposal - let mut bribe_coins: Vec = vec![]; for proposalid in all_proposals { - if proposalid <= max_proposal_claimed { - continue; - } + let mut bribe_coins: Vec = vec![]; + let claimed = VOTERS_CLAIM + .may_load(deps.storage, (address.clone(), proposalid))? + .unwrap_or_default(); let vote = match VOTERSPROPOSAL.may_load(deps.storage, (address.to_owned(), proposalid))? { Some(val) => val, None => continue, }; - for pair in vote.votes { let total_vote_weight = PROPOSALVOTE .load(deps.storage, (proposalid, pair.extended_pair))? @@ -444,11 +488,17 @@ pub fn calculate_bribe_reward_query( } } } + let response = RewardAllResponse { + proposal_id: proposalid, + total_incentive: bribe_coins, + claimed: claimed, + }; + resp.push(response); } //// send bank message to band - Ok(bribe_coins) + Ok(resp) } pub fn query_rebase_eligible( @@ -457,65 +507,216 @@ pub fn query_rebase_eligible( address: Addr, app_id: u64, denom: String, -) -> StdResult> { - let mut response: Vec = vec![]; +) -> StdResult> { + let mut response: Vec = vec![]; let all_proposals = match COMPLETEDPROPOSALS.may_load(deps.storage, app_id)? { Some(val) => val, None => vec![], }; for proposal_id_param in all_proposals { - let has_rebased = - REBASE_CLAIMED.may_load(deps.storage, (address.clone(), proposal_id_param))?; - if has_rebased.is_none() { - let proposal = PROPOSAL.load(deps.storage, proposal_id_param)?; - if !proposal.emission_completed { + let has_rebased = REBASE_CLAIMED + .may_load(deps.storage, (address.clone(), proposal_id_param))? + .unwrap_or_default(); + let proposal = PROPOSAL.load(deps.storage, proposal_id_param)?; + if !proposal.emission_completed { + continue; + } else { + let supply = SUPPLY + .may_load_at_height(deps.storage, &denom, proposal.height)? + .unwrap(); + + let total_locked: u128 = supply.token; + let total_rebase_amount: u128 = proposal.rebase_distributed; + let vtokens = match VTOKENS.may_load_at_height( + deps.storage, + (address.clone(), &denom), + proposal.height, + )? { + Some(val) => val, + None => vec![], + }; + if vtokens.is_empty() { continue; - } else { - let supply = SUPPLY - .may_load_at_height(deps.storage, &denom, proposal.height)? - .unwrap(); - if supply.token == 0 { - continue; - } - let total_locked: u128 = supply.token; - let total_rebase_amount: u128 = proposal.rebase_distributed; - let vtokens = match VTOKENS.may_load_at_height( - deps.storage, - (address.clone(), &denom), - proposal.height, - )? { - Some(val) => val, - None => vec![], - }; - if vtokens.is_empty() { - continue; - } - let mut locked_t1: u128 = 0; - let mut locked_t2: u128 = 0; + } + let mut locked_t1: u128 = 0; + let mut locked_t2: u128 = 0; - for vtoken in vtokens { - match vtoken.period { - LockingPeriod::T1 => locked_t1 += vtoken.token.amount.u128(), - LockingPeriod::T2 => locked_t2 += vtoken.token.amount.u128(), - } + for vtoken in vtokens { + match vtoken.period { + LockingPeriod::T1 => locked_t1 += vtoken.token.amount.u128(), + LockingPeriod::T2 => locked_t2 += vtoken.token.amount.u128(), } - let sum = locked_t1 + locked_t2; - let rebase_amount_param = (Uint128::from(total_rebase_amount) - .checked_mul(Uint128::from(sum))?) - .checked_div(Uint128::from(total_locked))?; - let rebase_response = RebaseResponse { - proposal_id: proposal_id_param, - rebase_amount: rebase_amount_param, - }; - response.push(rebase_response); } - } else { - continue; + let sum = locked_t1 + locked_t2; + let rebase_amount_param = (Uint128::from(total_rebase_amount) + .checked_mul(Uint128::from(sum))?) + .checked_div(Uint128::from(total_locked))?; + let rebase_response = RebaseAllResponse { + proposal_id: proposal_id_param, + rebase: rebase_amount_param, + claimed: has_rebased, + }; + response.push(rebase_response); } } Ok(response) } + +pub fn query_current_proposal_user( + deps: Deps, + _env: Env, + address: Addr, + app_id: u64, +) -> StdResult> { + let current_proposal = APPCURRENTPROPOSAL.may_load(deps.storage, app_id)?; + + let current_proposal = current_proposal.unwrap(); + let proposal = PROPOSAL.load(deps.storage, current_proposal)?; + + let mut resp = vec![]; + for ext_pair in proposal.extended_pair { + let mut user_vote = 0; + let mut user_vote_ratio = Decimal::zero(); + let mut total_incentive = vec![]; + let mut total_vote = 0; + let proposal_vote = PROPOSALVOTE.may_load(deps.storage, (current_proposal, ext_pair))?; + if let Some(..) = proposal_vote { + let proposal_vote = proposal_vote.unwrap(); + total_vote = proposal_vote.u128(); + } + let vote = VOTERSPROPOSAL.may_load(deps.storage, (address.clone(), current_proposal))?; + + if let Some(..) = vote { + let vote = vote.unwrap(); + let vote = vote.votes; + let vote_tmp = vote.into_iter().find(|x| x.extended_pair == ext_pair); + if let Some(..) = vote_tmp { + let vote_tmp = vote_tmp.unwrap(); + user_vote = vote_tmp.vote_weight; + user_vote_ratio = vote_tmp.vote_ratio; + } + } + //// load bribe//// + let bribe = BRIBES_BY_PROPOSAL.may_load(deps.storage, (current_proposal, ext_pair))?; + if let Some(..) = bribe { + let bribe = bribe.unwrap(); + total_incentive = bribe; + } + let vote_response = VoteResponse { + pair: ext_pair, + user_vote: user_vote, + user_vote_ratio: user_vote_ratio, + total_incentive: total_incentive, + total_vote: total_vote, + }; + resp.push(vote_response); + } + Ok(resp) +} + +pub fn query_delegation( + deps: Deps, + _env: Env, + delegated_address: Addr, + delegator_address: Addr, + height: Option, +) -> StdResult> { + if height.is_some() { + let delegation_user = + DELEGATED.may_load_at_height(deps.storage, delegator_address, height.unwrap())?; + if delegation_user.is_none() { + return Ok(None); + } + let delegation_user = delegation_user.unwrap(); + let delegation = delegation_user + .delegations + .into_iter() + .find(|x| x.delegated_to == delegated_address); + Ok(delegation) + } else { + let delegation_user = DELEGATED.may_load(deps.storage, delegator_address)?; + if delegation_user.is_none() { + return Ok(None); + } + let delegation_user = delegation_user.unwrap(); + let delegation = delegation_user + .delegations + .into_iter() + .find(|x| x.delegated_to == delegated_address); + Ok(delegation) + } +} +pub fn query_delegator_param( + deps: Deps, + _env: Env, + delegated_address: Addr, +) -> StdResult> { + let delegation_info = DELEGATION_INFO.may_load(deps.storage, delegated_address)?; + Ok(delegation_info) +} + +pub fn query_delegated_stats( + deps: Deps, + _env: Env, + delegated_address: Addr, + height: Option, +) -> StdResult> { + if height.is_some() { + let delegation_stats = DELEGATION_STATS.may_load_at_height( + deps.storage, + delegated_address, + height.unwrap(), + )?; + return Ok(delegation_stats); + } else { + let delegation_stats = DELEGATION_STATS.may_load(deps.storage, delegated_address)?; + return Ok(delegation_stats); + } +} + +pub fn query_user_delegation_all( + deps: Deps, + _env: Env, + delegator_address: Addr, + height: Option, +) -> StdResult> { + if height.is_some() { + let user_delegation_info = + DELEGATED.may_load_at_height(deps.storage, delegator_address, height.unwrap())?; + return Ok(user_delegation_info); + } else { + let user_delegation_info = DELEGATED.may_load(deps.storage, delegator_address)?; + return Ok(user_delegation_info); + } +} + +pub fn query_emission_voting_power( + deps: Deps, + _env: Env, + address: Addr, + proposal_id: u64, + denom: String, +) -> StdResult { + let proposal = PROPOSAL.may_load(deps.storage, proposal_id)?.unwrap(); + let vtokens = + VTOKENS.may_load_at_height(deps.storage, (address.clone(), &denom), proposal.height)?; + if vtokens.is_none() { + return Ok(0); + } + let mut vote_power: u128 = 0; + for vtoken in vtokens.unwrap() { + vote_power += vtoken.token.amount.u128(); + } + + let delegation = DELEGATED.may_load_at_height(deps.storage, address, proposal.height)?; + if let Some(..) = delegation { + let delegation = delegation.unwrap(); + vote_power -= delegation.total_casted; + } + Ok(vote_power) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/state.rs b/src/state.rs index 68632ac..329cf32 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; pub const VOTEPOWER: SnapshotMap<(&Addr, String), Uint128> = SnapshotMap::new( "voters_key", - "voters_checkpoints", - "voters_changelogs", + "voters_power_checkpoints", + "voters_power_changelogs", Strategy::EveryBlock, ); @@ -36,7 +36,7 @@ pub enum Status { Unlocked, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] #[serde(rename_all = "snake_case")] pub struct Vtoken { /// amount of token being locked @@ -109,7 +109,7 @@ pub const VTOKENS: SnapshotMap<(Addr, &str), Vec> = SnapshotMap::new( Strategy::EveryBlock, ); -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] pub struct Proposal { pub app_id: u64, pub voting_start_time: Timestamp, @@ -177,9 +177,39 @@ pub struct DelegationInfo { pub delegated_address: Addr, /// syndicate address pub delegated_name: String, //// syndicate name - pub fee_collector_adress: Addr, + pub fee_collector_address: Addr, pub protocol_fees: Decimal, // fixed pub delegator_fees: Decimal, // variable fee charged by the syndicate from delegator + pub excluded_fee_pair: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] +pub struct VoteResponse { + pub pair: u64, + pub total_incentive: Vec, + pub user_vote: u128, + pub user_vote_ratio: Decimal, + pub total_vote: u128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] +pub struct RewardAllResponse { + pub proposal_id: u64, + pub total_incentive: Vec, + pub claimed: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] +pub struct RebaseAllResponse { + pub proposal_id: u64, + pub rebase: Uint128, + pub claimed: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] +pub struct DelegationStats { + pub total_delegated: u128, + pub total_delegators: u64, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Eq)] @@ -206,8 +236,17 @@ pub const EMISSION_REWARD: Map = Map::new("emission_rewa pub const VOTERS_VOTE: Map<(Addr, u64), bool> = Map::new("voters_vote"); +pub const VOTERS_CLAIM: Map<(Addr, u64), bool> = Map::new("voters_claim"); + +pub const DELEGATOR_CLAIM: Map<(Addr, u64), bool> = Map::new("delegator_claim"); + pub const VOTERSPROPOSAL: Map<(Addr, u64), Vote> = Map::new("voters_proposal"); +pub const VOTERS_CLAIMED_PROPOSALS: Map> = Map::new("voters_claimed_proposals"); + +pub const DELEGATOR_CLAIMED_PROPOSALS: Map> = + Map::new("delegator_claimed_proposals"); + pub const MAXPROPOSALCLAIMED: Map<(u64, Addr), u64> = Map::new("max_proposal_claimed"); pub const COMPLETEDPROPOSALS: Map> = Map::new("completed_proposals"); @@ -216,14 +255,21 @@ pub const REBASE_CLAIMED: Map<(Addr, u64), bool> = Map::new("rebase_claimed"); pub const DELEGATED: SnapshotMap = SnapshotMap::new( "delegated", - "voters_checkpoints", - "voters_changelogs", + "delegated_checkpoints", + "delegated_changelogs", Strategy::EveryBlock, ); pub const DELEGATION_INFO: SnapshotMap = SnapshotMap::new( "delegation_info", - "voters_checkpoints", - "voters_changelogs", + "delegation_info_checkpoints", + "delegation_info_changelogs", + Strategy::EveryBlock, +); + +pub const DELEGATION_STATS: SnapshotMap = SnapshotMap::new( + "delegation_stats", + "delegation_stats_checkpoints", + "delegation_stats_changelogs", Strategy::EveryBlock, ); diff --git a/src/test.rs b/src/test.rs index 0850fdc..6b96127 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,4 +1,3 @@ - use crate::contract::*; use crate::error::ContractError; use crate::helpers::{ @@ -478,96 +477,96 @@ fn withdraw_denom_not_locked() { }; } -#[test] -fn transfer_different_denom() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info("owner", &[]); - - let imsg = init_msg(); - instantiate(deps.as_mut(), env.clone(), info.clone(), imsg.clone()).unwrap(); +// #[test] +// fn transfer_different_denom() { +// let mut deps = mock_dependencies(); +// let env = mock_env(); +// let info = mock_info("owner", &[]); - let owner = Addr::unchecked("owner"); - let recipient = Addr::unchecked("recipient"); +// let imsg = init_msg(); +// instantiate(deps.as_mut(), env.clone(), info.clone(), imsg.clone()).unwrap(); - let denom1 = "DNM1"; - let denom2 = "DNM2"; +// let owner = Addr::unchecked("owner"); +// let recipient = Addr::unchecked("recipient"); - // Create token for recipient - let info = mock_info("recipient", &coins(100, denom2.to_string())); - handle_lock_nft( - deps.as_mut(), - env.clone(), - info, - 12, - LockingPeriod::T1, - None, - ) - .unwrap(); - - // Create tokens for owner == sender - let info = mock_info("owner", &coins(100, denom1.to_string())); - handle_lock_nft( - deps.as_mut(), - env.clone(), - info.clone(), - 12, - LockingPeriod::T1, - None, - ) - .unwrap(); +// let denom1 = "DNM1"; +// let denom2 = "DNM2"; - // create a copy of owner's vtoken to compare and check if the recipient's - // vtoken is the same. - let locked_vtokens = VTOKENS - .load(deps.as_ref().storage, (owner.clone(), denom1)) - .unwrap(); - - let msg = ExecuteMsg::Transfer { - recipient: recipient.to_string(), - locking_period: LockingPeriod::T1, - denom: denom1.to_string(), - }; +// // Create token for recipient +// let info = mock_info("recipient", &coins(100, denom2.to_string())); +// handle_lock_nft( +// deps.as_mut(), +// env.clone(), +// info, +// 12, +// LockingPeriod::T1, +// None, +// ) +// .unwrap(); - let info = mock_info(owner.as_str(), &[]); - let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); - assert_eq!(res.messages.len(), 0); - assert_eq!(res.attributes.len(), 3); +// // Create tokens for owner == sender +// let info = mock_info("owner", &coins(100, denom1.to_string())); +// handle_lock_nft( +// deps.as_mut(), +// env.clone(), +// info.clone(), +// 12, +// LockingPeriod::T1, +// None, +// ) +// .unwrap(); - // Check correct update in sender vtokens - let res = VTOKENS - .load(deps.as_ref().storage, (owner.clone(), denom1)) - .unwrap_err(); - match res { - StdError::NotFound { .. } => {} - e => panic!("{:?}", e), - } +// // create a copy of owner's vtoken to compare and check if the recipient's +// // vtoken is the same. +// let locked_vtokens = VTOKENS +// .load(deps.as_ref().storage, (owner.clone(), denom1)) +// .unwrap(); - // Check correct update in recipient vtokens - { - let res = VTOKENS - .load(deps.as_ref().storage, (recipient.clone(), denom1)) - .unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(res[0], locked_vtokens[0]); - - let res = VTOKENS - .load(deps.as_ref().storage, (recipient.clone(), denom2)) - .unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(res[0].token.amount.u128(), 100); - assert_eq!(res[0].token.denom, denom2.to_string()); - } +// let msg = ExecuteMsg::Transfer { +// recipient: recipient.to_string(), +// locking_period: LockingPeriod::T1, +// denom: denom1.to_string(), +// }; - // Check correct update in recipient nft - let recipient_nft = VTOKENS - .load(deps.as_ref().storage, (recipient.clone(), "DNM1")) - .unwrap(); +// let info = mock_info(owner.as_str(), &[]); +// let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); +// assert_eq!(res.messages.len(), 0); +// assert_eq!(res.attributes.len(), 3); + +// // Check correct update in sender vtokens +// let res = VTOKENS +// .load(deps.as_ref().storage, (owner.clone(), denom1)) +// .unwrap_err(); +// match res { +// StdError::NotFound { .. } => {} +// e => panic!("{:?}", e), +// } + +// // Check correct update in recipient vtokens +// { +// let res = VTOKENS +// .load(deps.as_ref().storage, (recipient.clone(), denom1)) +// .unwrap(); +// assert_eq!(res.len(), 1); +// assert_eq!(res[0], locked_vtokens[0]); - assert_eq!(recipient_nft.len(), 1); - assert_eq!(recipient_nft[0].token.amount.u128(), 100); - assert_eq!(recipient_nft[0].token.denom, denom1.to_string()); -} +// let res = VTOKENS +// .load(deps.as_ref().storage, (recipient.clone(), denom2)) +// .unwrap(); +// assert_eq!(res.len(), 1); +// assert_eq!(res[0].token.amount.u128(), 100); +// assert_eq!(res[0].token.denom, denom2.to_string()); +// } + +// // Check correct update in recipient nft +// let recipient_nft = VTOKENS +// .load(deps.as_ref().storage, (recipient.clone(), "DNM1")) +// .unwrap(); + +// assert_eq!(recipient_nft.len(), 1); +// assert_eq!(recipient_nft[0].token.amount.u128(), 100); +// assert_eq!(recipient_nft[0].token.denom, denom1.to_string()); +// } #[test] fn transfer_same_denom() { @@ -809,7 +808,7 @@ fn test_vote_proposal_with_wrong_extended_pair() { assert_eq!( err, Err(ContractError::CustomError { - val: "Invalid Extended pair".to_string() + val: "Extended pair does not exist in proposal".to_string() }) ); } @@ -963,7 +962,7 @@ fn test_bribe_proposal_multiple_denoms() { assert_eq!( err, Err(ContractError::CustomError { - val: "Multiple denominations are not supported as yet.".to_string() + val: "Multiple denominations are not supported".to_string() }) ); } @@ -1428,5 +1427,5 @@ fn test_change_vote() { .load(deps.as_ref().storage, (info.sender.clone(), 1)) .unwrap(); assert_eq!(vote_weight.votes[0].extended_pair, 2); - assert_eq!(vote_weight.votes[0].vote_weight, 50u128); + assert_eq!(vote_weight.votes[0].vote_weight, 25u128); }