Skip to content

Commit

Permalink
Native undelegation and burn
Browse files Browse the repository at this point in the history
  • Loading branch information
maurolacy committed Oct 26, 2023
1 parent 035587e commit ae0d9a5
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 2 deletions.
89 changes: 89 additions & 0 deletions contracts/provider/native-staking-proxy/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use cosmwasm_std::BankMsg::Burn;
use cosmwasm_std::WasmMsg::Execute;
use cosmwasm_std::{
coin, ensure_eq, to_binary, Coin, DistributionMsg, GovMsg, Response, StakingMsg, VoteOption,
WeightedVoteOption,
};
use cw2::set_contract_version;
use cw_storage_plus::Item;
use std::cmp::min;

use cw_utils::{must_pay, nonpayable};
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
Expand Down Expand Up @@ -83,6 +85,93 @@ impl NativeStakingProxyContract<'_> {
Ok(Response::new().add_message(msg))
}

/// Burn `amount` tokens from the given validator, if set.
/// If `validator` is not set, undelegate evenly from all validators the user has stake in.
/// Can only be called by the parent contract
#[msg(exec)]
fn burn(
&self,
ctx: ExecCtx,
validator: Option<String>,
amount: Coin,
) -> Result<Response, ContractError> {
let cfg = self.config.load(ctx.deps.storage)?;
ensure_eq!(cfg.parent, ctx.info.sender, ContractError::Unauthorized {});

nonpayable(&ctx.info)?;

let validators = match validator {
Some(validator) => {
let validator = ctx
.deps
.querier
.query_delegation(ctx.env.contract.address.clone(), validator)?
.map(|full_delegation| {
(full_delegation.validator, full_delegation.amount.amount)
})
.unwrap();
vec![validator]
}
None => {
let validators = ctx
.deps
.querier
.query_all_delegations(ctx.env.contract.address.clone())?
.iter()
.map(|delegation| (delegation.validator.clone(), delegation.amount.amount))
.collect::<Vec<_>>();
validators
}
};

let mut unstake_msgs = vec![];
let mut unstaked = 0;
let proportional_amount = amount.amount.u128() / validators.len() as u128;
for (validator, delegated_amount) in &validators {
// Check validator has `proportional_amount` delegated. Adjust accordingly if not.
let unstake_amount = min(delegated_amount.u128(), proportional_amount);
let unstake_msg = StakingMsg::Undelegate {
validator: validator.to_string(),
amount: coin(unstake_amount, &cfg.denom),
};
unstaked += unstake_amount;
unstake_msgs.push(unstake_msg);
}
// Adjust possible rounding issues
if unstaked < amount.amount.u128() {
// Look for the first validator that has enough stake, and unstake it from there
let unstake_amount = amount.amount.u128() - unstaked;
for (validator, delegated_amount) in &validators {
if delegated_amount.u128() >= unstake_amount + proportional_amount {
let unstake_msg = StakingMsg::Undelegate {
validator: validator.to_string(),
amount: coin(unstake_amount, &cfg.denom),
};
unstaked += unstake_amount;
unstake_msgs.push(unstake_msg);
break;
}
}
}
// Bail if we still don't have enough stake
if unstaked < amount.amount.u128() {
return Err(ContractError::InsufficientDelegations(
ctx.env.contract.address.to_string(),
amount.amount,
));
}

// Burn stake
// FIXME? Accounting trick to avoid burning
let burn_msg = Burn {
amount: vec![amount],
};

Ok(Response::new()
.add_messages(unstake_msgs)
.add_message(burn_msg))
}

/// Re-stakes the given amount from the one validator to another on behalf of the calling user.
/// Returns an error if the user doesn't have such stake
#[msg(exec)]
Expand Down
3 changes: 3 additions & 0 deletions contracts/provider/native-staking-proxy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ pub enum ContractError {

#[error("Validator {0} has not enough delegated funds: {1}")]
InsufficientDelegation(String, Uint128),

#[error("Native proxy {0} has not enough delegated funds: {1}")]
InsufficientDelegations(String, Uint128),
}
3 changes: 3 additions & 0 deletions contracts/provider/native-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub enum ContractError {
#[error("Missing instantiate reply data")]
NoInstantiateData {},

#[error("Missing proxy contract for {0}")]
NoProxy(String),

#[error("You cannot use a max slashing rate over 1.0 (100%)")]
InvalidMaxSlashing,
}
23 changes: 21 additions & 2 deletions contracts/provider/native-staking/src/local_staking_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,28 @@ impl LocalStakingApi for NativeStakingContract<'_> {
// Assert no funds are passed in
nonpayable(&ctx.info)?;

let _ = (owner, amount, validator);
let owner_addr = ctx.deps.api.addr_validate(&owner)?;

todo!()
// Look up if there is a proxy to match. Fail or call burn on existing
match self
.proxy_by_owner
.may_load(ctx.deps.storage, &owner_addr)?
{
None => Err(ContractError::NoProxy(owner)),
Some(proxy_addr) => {
// Send burn message to the proxy contract
let msg = to_binary(&mesh_native_staking_proxy::contract::ExecMsg::Burn {
validator,
amount,
})?;
let wasm_msg = WasmMsg::Execute {
contract_addr: proxy_addr.into(),
msg,
funds: ctx.info.funds,
};
Ok(Response::new().add_message(wasm_msg))
}
}
}

/// Returns the maximum percentage that can be slashed
Expand Down

0 comments on commit ae0d9a5

Please sign in to comment.