Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Claims controller #200

Merged
merged 4 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions contracts/cw20-staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ library = []
cw0 = { path = "../../packages/cw0", version = "0.3.2" }
cw2 = { path = "../../packages/cw2", version = "0.3.2" }
cw20 = { path = "../../packages/cw20", version = "0.3.2" }
cw-controllers = { path = "../../packages/controllers", version = "0.3.2" }
cw20-base = { path = "../../contracts/cw20-base", version = "0.3.2", features = ["library"] }
cosmwasm-std = { version = "0.12.2", features = ["staking"] }
cosmwasm-storage = { version = "0.12.2" }
Expand Down
28 changes: 11 additions & 17 deletions contracts/cw20-staking/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use cosmwasm_std::{
InitResponse, MessageInfo, QuerierWrapper, StakingMsg, StdError, StdResult, Uint128, WasmMsg,
};

use cw0::claim::{claim_tokens, create_claim, CLAIMS};
use cw2::set_contract_version;
use cw20_base::allowances::{
handle_burn_from, handle_decrease_allowance, handle_increase_allowance, handle_send_from,
Expand All @@ -15,9 +14,9 @@ use cw20_base::contract::{
use cw20_base::state::{token_info, MinterData, TokenInfo};

use crate::error::ContractError;
use crate::msg::{ClaimsResponse, HandleMsg, InitMsg, InvestmentResponse, QueryMsg};
use crate::msg::{HandleMsg, InitMsg, InvestmentResponse, QueryMsg};
use crate::state::{
invest_info, invest_info_read, total_supply, total_supply_read, InvestmentInfo, Supply,
invest_info, invest_info_read, total_supply, total_supply_read, InvestmentInfo, Supply, CLAIMS,
};

const FALLBACK_RATIO: Decimal = Decimal::one();
Expand Down Expand Up @@ -269,7 +268,7 @@ pub fn unbond(
supply.claims += unbond;
totals.save(&supply)?;

create_claim(
CLAIMS.create_claim(
deps.storage,
&sender_raw,
unbond,
Expand Down Expand Up @@ -307,7 +306,8 @@ pub fn claim(deps: DepsMut, env: Env, info: MessageInfo) -> Result<HandleRespons
// check how much to send - min(balance, claims[sender]), and reduce the claim
// Ensure we have enough balance to cover this and only send some claims if that is all we can cover
let sender_raw = deps.api.canonical_address(&info.sender)?;
let to_send = claim_tokens(deps.storage, &sender_raw, &env.block, Some(balance.amount))?;
let to_send =
CLAIMS.claim_tokens(deps.storage, &sender_raw, &env.block, Some(balance.amount))?;
if to_send == Uint128(0) {
return Err(ContractError::NothingToClaim {});
}
Expand Down Expand Up @@ -417,7 +417,7 @@ pub fn _bond_all_tokens(
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
// custom queries
QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?),
QueryMsg::Claims { address } => to_binary(&CLAIMS.query_claims(deps, address)?),
QueryMsg::Investment {} => to_binary(&query_investment(deps)?),
// inherited from cw20-base
QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
Expand All @@ -428,14 +428,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
}
}

pub fn query_claims(deps: Deps, address: HumanAddr) -> StdResult<ClaimsResponse> {
let address_raw = deps.api.canonical_address(&address)?;
let claims = CLAIMS
.may_load(deps.storage, &address_raw)?
.unwrap_or_default();
Ok(ClaimsResponse { claims })
}

pub fn query_investment(deps: Deps) -> StdResult<InvestmentResponse> {
let invest = invest_info_read(deps.storage).load()?;
let supply = total_supply_read(deps.storage).load()?;
Expand All @@ -459,12 +451,14 @@ pub fn query_investment(deps: Deps) -> StdResult<InvestmentResponse> {
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;

use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR,
};
use cosmwasm_std::{coins, Coin, CosmosMsg, Decimal, FullDelegation, Validator};
use cw0::{claim::Claim, Duration, DAY, HOUR, WEEK};
use std::str::FromStr;
use cw0::{Duration, DAY, HOUR, WEEK};
use cw_controllers::Claim;

fn sample_validator<U: Into<HumanAddr>>(addr: U) -> Validator {
Validator {
Expand Down Expand Up @@ -529,7 +523,7 @@ mod tests {
}

fn get_claims<U: Into<HumanAddr>>(deps: Deps, addr: U) -> Vec<Claim> {
query_claims(deps, addr.into()).unwrap().claims
CLAIMS.query_claims(deps, addr.into()).unwrap().claims
}

#[test]
Expand Down
8 changes: 2 additions & 6 deletions contracts/cw20-staking/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::{Binary, Coin, Decimal, HumanAddr, Uint128};
use cw0::{claim::Claim, Duration};
use cw0::Duration;
use cw20::Expiration;
pub use cw_controllers::ClaimsResponse;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InitMsg {
Expand Down Expand Up @@ -116,11 +117,6 @@ pub enum QueryMsg {
},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ClaimsResponse {
pub claims: Vec<Claim>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InvestmentResponse {
pub token_supply: Uint128,
Expand Down
3 changes: 3 additions & 0 deletions contracts/cw20-staking/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use serde::{Deserialize, Serialize};
use cosmwasm_std::{CanonicalAddr, Decimal, HumanAddr, Storage, Uint128};
use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton};
use cw0::Duration;
use cw_controllers::Claims;

pub const CLAIMS: Claims = Claims::new("claims");

pub const KEY_INVESTMENT: &[u8] = b"invest";
pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply";
Expand Down
12 changes: 5 additions & 7 deletions contracts/cw4-stake/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ use cw_storage_plus::Bound;

use crate::error::ContractError;
use crate::msg::{ClaimsResponse, HandleMsg, InitMsg, QueryMsg, StakedResponse};
use crate::state::{Config, ADMIN, CONFIG, HOOKS, MEMBERS, STAKE, TOTAL};
use cw0::claim::{claim_tokens, create_claim, CLAIMS};
use crate::state::{Config, ADMIN, CLAIMS, CONFIG, HOOKS, MEMBERS, STAKE, TOTAL};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw4-stake";
Expand Down Expand Up @@ -126,7 +125,7 @@ pub fn handle_unbond(

// provide them a claim
let cfg = CONFIG.load(deps.storage)?;
create_claim(
CLAIMS.create_claim(
deps.storage,
&sender_raw,
amount,
Expand Down Expand Up @@ -198,7 +197,7 @@ pub fn handle_claim(
info: MessageInfo,
) -> Result<HandleResponse, ContractError> {
let sender_raw = deps.api.canonical_address(&info.sender)?;
let release = claim_tokens(deps.storage, &sender_raw, &env.block, None)?;
let release = CLAIMS.claim_tokens(deps.storage, &sender_raw, &env.block, None)?;
if release.is_zero() {
return Err(ContractError::NothingToClaim {});
}
Expand Down Expand Up @@ -230,7 +229,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
to_binary(&list_members(deps, start_after, limit)?)
}
QueryMsg::TotalWeight {} => to_binary(&query_total_weight(deps)?),
QueryMsg::Claims { address } => to_binary(&query_claims(deps, address)?),
QueryMsg::Claims { address } => to_binary(&CLAIMS.query_claims(deps, address)?),
QueryMsg::Staked { address } => to_binary(&query_staked(deps, address)?),
QueryMsg::Admin {} => to_binary(&ADMIN.query_admin(deps)?),
QueryMsg::Hooks {} => to_binary(&HOOKS.query_hooks(deps)?),
Expand Down Expand Up @@ -304,10 +303,9 @@ mod tests {
use super::*;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{from_slice, Api, StdError, Storage};
use cw0::claim::Claim;
use cw0::Duration;
use cw4::{member_key, TOTAL_KEY};
use cw_controllers::{AdminError, HookError};
use cw_controllers::{AdminError, Claim, HookError};

const INIT_ADMIN: &str = "juan";
const USER1: &str = "somebody";
Expand Down
7 changes: 1 addition & 6 deletions contracts/cw4-stake/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::{Coin, HumanAddr, Uint128};
use cw0::claim::Claim;
use cw0::Duration;
pub use cw_controllers::ClaimsResponse;

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct InitMsg {
Expand Down Expand Up @@ -68,11 +68,6 @@ pub enum QueryMsg {
Hooks {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ClaimsResponse {
pub claims: Vec<Claim>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StakedResponse {
pub stake: Coin,
Expand Down
4 changes: 3 additions & 1 deletion contracts/cw4-stake/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ use serde::{Deserialize, Serialize};
use cosmwasm_std::Uint128;
use cw0::Duration;
use cw4::TOTAL_KEY;
use cw_controllers::{Admin, Hooks};
use cw_controllers::{Admin, Claims, Hooks};
use cw_storage_plus::{Item, Map, SnapshotMap, Strategy};

pub const CLAIMS: Claims = Claims::new("claims");

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct Config {
/// denom of the token to stake
Expand Down
107 changes: 107 additions & 0 deletions packages/controllers/src/claim.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::{BlockInfo, CanonicalAddr, Deps, HumanAddr, StdResult, Storage, Uint128};
use cw0::Expiration;
use cw_storage_plus::Map;
use std::ops::Deref;

// TODO: pull into cw0?
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ClaimsResponse {
pub claims: Vec<Claim>,
}

// TODO: pull into cw0?
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Claim {
pub amount: Uint128,
pub release_at: Expiration,
}

impl Claim {
pub fn new(amount: u128, released: Expiration) -> Self {
Claim {
amount: amount.into(),
release_at: released,
}
}
}

// TODO: revisit design (split each claim on own key?)
pub struct Claims<'a>(Map<'a, &'a [u8], Vec<Claim>>);

// allow easy access to the basic Item operations if desired
// TODO: reconsider if we need this here, maybe only for maps?
impl<'a> Deref for Claims<'a> {
type Target = Map<'a, &'a [u8], Vec<Claim>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'a> Claims<'a> {
pub const fn new(storage_key: &'a str) -> Self {
Claims(Map::new(storage_key))
}

/// This creates a claim, such that the given address can claim an amount of tokens after
/// the release date.
pub fn create_claim(
&self,
storage: &mut dyn Storage,
addr: &CanonicalAddr,
amount: Uint128,
release_at: Expiration,
) -> StdResult<()> {
// add a claim to this user to get their tokens after the unbonding period
self.update(storage, &addr, |old| -> StdResult<_> {
let mut claims = old.unwrap_or_default();
claims.push(Claim { amount, release_at });
Ok(claims)
})?;
Ok(())
}

/// This iterates over all mature claims for the address, and removes them, up to an optional cap.
/// it removes the finished claims and returns the total amount of tokens to be released.
pub fn claim_tokens(
&self,
storage: &mut dyn Storage,
addr: &CanonicalAddr,
block: &BlockInfo,
cap: Option<Uint128>,
) -> StdResult<Uint128> {
let mut to_send = Uint128(0);
self.update(storage, &addr, |claim| -> StdResult<_> {
let (_send, waiting): (Vec<_>, _) =
claim.unwrap_or_default().iter().cloned().partition(|c| {
// if mature and we can pay fully, then include in _send
if c.release_at.is_expired(block) {
if let Some(limit) = cap {
if to_send + c.amount > limit {
return false;
}
}
// TODO: handle partial paying claims?
to_send += c.amount;
true
} else {
// not to send, leave in waiting and save again
false
}
});
Ok(waiting)
})?;
Ok(to_send)
}

pub fn query_claims(&self, deps: Deps, address: HumanAddr) -> StdResult<ClaimsResponse> {
let address_raw = deps.api.canonical_address(&address)?;
let claims = self
.may_load(deps.storage, &address_raw)?
.unwrap_or_default();
Ok(ClaimsResponse { claims })
}
}
2 changes: 2 additions & 0 deletions packages/controllers/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod admin;
mod claim;
mod hooks;

pub use admin::{Admin, AdminError, AdminResponse};
pub use claim::{Claim, Claims, ClaimsResponse};
pub use hooks::{HookError, Hooks};
1 change: 0 additions & 1 deletion packages/cw0/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ documentation = "https://docs.cosmwasm.com"

[dependencies]
cosmwasm-std = { version = "0.12.2" }
cw-storage-plus = { path = "../storage-plus", version = "0.3.2", features = ["iterator"] }
schemars = "0.7"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.21" }
Loading