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

Lunchtime stab at adding temporary halts on payments #6

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
109 changes: 86 additions & 23 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Response,
StdResult, WasmMsg,
to_binary, Addr, Binary, Coin, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order,
Response, StdResult, WasmMsg,
};

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, Payment, PaymentsResponse, QueryMsg};
use crate::state::{next_id, PaymentState, PAYMENTS};
use crate::state::{next_id, PaymentState, ADMIN_ADDRESS, HALTED_PAYEES, PAYMENTS};
use cw20::Cw20ExecuteMsg;

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -17,6 +17,12 @@ pub fn instantiate(
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
// save contract admin
let admin_addr = deps.api.addr_validate(&msg.admin_address)?;

ADMIN_ADDRESS.save(deps.storage, &admin_addr)?;

// save payments
for p in msg.schedule.into_iter() {
let id = next_id(deps.storage)?;
PAYMENTS.save(
Expand All @@ -37,21 +43,66 @@ pub fn instantiate(
pub fn execute(
deps: DepsMut,
env: Env,
_info: MessageInfo,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Pay {} => execute_pay(deps, env),
ExecuteMsg::HaltPayments { recipient } => halt_payments(deps, env, info, recipient),
ExecuteMsg::StartPayments { recipient } => start_payments(deps, env, info, recipient),
}
}

// allows for payments to be temporarily halted
// but does not clear them in case they are restarted later
pub fn halt_payments(
deps: DepsMut,
_env: Env,
info: MessageInfo,
recipient: String,
) -> Result<Response, ContractError> {
let contract_admin = ADMIN_ADDRESS.load(deps.storage)?;

if info.sender != contract_admin {
return Err(ContractError::Unauthorized {});
}

let recipient_address = deps.api.addr_validate(&recipient)?;
HALTED_PAYEES.save(deps.storage, recipient_address, &Empty {})?;
Ok(Response::new().add_attribute("payee_halted", recipient))
}

pub fn start_payments(
deps: DepsMut,
_env: Env,
info: MessageInfo,
recipient: String,
) -> Result<Response, ContractError> {
let contract_admin = ADMIN_ADDRESS.load(deps.storage)?;

if info.sender != contract_admin {
return Err(ContractError::Unauthorized {});
}

let recipient_address = deps.api.addr_validate(&recipient)?;
HALTED_PAYEES.remove(deps.storage, recipient_address);
Ok(Response::new().add_attribute("payee", recipient))
}

pub fn payments_halted_for(deps: Deps, address: Addr) -> Option<Addr> {
let res = HALTED_PAYEES.may_load(deps.storage, address.clone()).ok()?;

res.map(|_| address)
}

pub fn execute_pay(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
let to_be_paid: Vec<PaymentState> = PAYMENTS
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|r| match r {
Ok(r) => Some(r.1),
_ => None,
})
.filter(|p| payments_halted_for(deps.as_ref(), p.payment.recipient.clone()).is_none())
.filter(|p| !p.paid && p.payment.time.is_expired(&env.block))
.collect();

Expand Down Expand Up @@ -120,7 +171,15 @@ fn query_payments(deps: Deps) -> PaymentsResponse {
payments: PAYMENTS
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|p| match p {
Ok(p) => Some(p.1),
Ok(p) => {
// if they are not in halted payees,
// then the payments should occur
let is_halted = payments_halted_for(deps, p.1.payment.recipient.clone());
match is_halted {
Some(_) => None,
None => Some(p.1),
}
}
Err(_) => None,
})
.collect(),
Expand All @@ -134,18 +193,15 @@ mod tests {
use cosmwasm_std::{coin, coins, from_binary, Addr, Empty, Uint128};
use cw0::Expiration;
use cw20::{Cw20Coin, Cw20Contract};
use cw_multi_test::{
next_block, App, AppResponse, BankKeeper, Contract, ContractWrapper, Executor,
};
use std::borrow::Cow::Owned;
use std::fmt::Error;
use cw_multi_test::{next_block, App, BankKeeper, Contract, ContractWrapper, Executor};

const ADMIN: &str = "admin-address";
const OWNER: &str = "owner0001";
const FUNDER: &str = "funder";
const PAYEE2: &str = "payee0002";
const PAYEE3: &str = "payee0003";

const NATIVE_TOKEN_DENOM: &str = "ujuno";
// const NATIVE_TOKEN_DENOM: &str = "ujuno";
const INITIAL_BALANCE: u128 = 2000000;

pub fn contract_vest() -> Box<dyn Contract<Empty>> {
Expand Down Expand Up @@ -208,7 +264,10 @@ mod tests {

fn instantiate_vest(app: &mut App, payments: Vec<Payment>) -> Addr {
let flex_id = app.store_code(contract_vest());
let msg = crate::msg::InstantiateMsg { schedule: payments };
let msg = crate::msg::InstantiateMsg {
schedule: payments,
admin_address: ADMIN.to_string(),
};
app.instantiate_contract(flex_id, Addr::unchecked(OWNER), &msg, &[], "flex", None)
.unwrap()
}
Expand All @@ -223,7 +282,7 @@ mod tests {
}

fn fund_vest_contract(app: &mut App, vest: Addr, cw20: Addr, funder: Addr, amount: Uint128) {
app.execute_contract(
let _ = app.execute_contract(
funder,
cw20,
&Cw20ExecuteMsg::Transfer {
Expand All @@ -238,7 +297,10 @@ mod tests {
fn proper_initialization() {
let mut deps = mock_dependencies(&[]);

let msg = InstantiateMsg { schedule: vec![] };
let msg = InstantiateMsg {
schedule: vec![],
admin_address: ADMIN.to_string(),
};
let info = mock_info("creator", &coins(1000, "earth"));

// we can just call .unwrap() to assert this was a success
Expand All @@ -265,6 +327,7 @@ mod tests {
let payment2 = payment.clone();
let msg = InstantiateMsg {
schedule: vec![payment.clone(), payment2],
admin_address: ADMIN.to_string(),
};
let info = mock_info("creator", &coins(1000, "earth"));

Expand All @@ -282,10 +345,10 @@ mod tests {
fn proper_initialization_integration() {
let mut app = mock_app();

let (owner, funder, _payee2, _payee3) = get_accounts();
let (owner, _funder, _payee2, _payee3) = get_accounts();

let cw20_addr = instantiate_cw20(&mut app);
let cw20 = Cw20Contract(cw20_addr.clone());
let _cw20 = Cw20Contract(cw20_addr.clone());

let payments = vec![Payment {
recipient: owner,
Expand All @@ -295,7 +358,7 @@ mod tests {
time: Default::default(),
}];

let vest_addr = instantiate_vest(&mut app, payments);
let _vest_addr = instantiate_vest(&mut app, payments);
}

#[test]
Expand Down Expand Up @@ -486,7 +549,7 @@ mod tests {
fn single_native_payment() {
let mut app = mock_app();

let (owner, funder, _payee2, _payee3) = get_accounts();
let (owner, _funder, _payee2, _payee3) = get_accounts();

let denom = String::from("ujuno");
let payments = vec![Payment {
Expand All @@ -500,7 +563,7 @@ mod tests {
let vest_addr = instantiate_vest(&mut app, payments);

// Fund vest contract
app.init_bank_balance(&vest_addr, vec![coin(1, denom.clone())]);
let _ = app.init_bank_balance(&vest_addr, vec![coin(1, denom.clone())]);

let owner_balance = |app: &App<Empty>| {
app.wrap()
Expand Down Expand Up @@ -531,10 +594,10 @@ mod tests {
fn multiple_native_payment() {
let mut app = mock_app();

let (owner, funder, _payee2, _payee3) = get_accounts();
let (owner, _funder, _payee2, _payee3) = get_accounts();

let cw20_addr = instantiate_cw20(&mut app);
let cw20 = Cw20Contract(cw20_addr.clone());
let _cw20 = Cw20Contract(cw20_addr.clone());

let current_height = app.block_info().height;

Expand Down Expand Up @@ -573,7 +636,7 @@ mod tests {
let vest_addr = instantiate_vest(&mut app, payments);

// Fund vest contract
app.init_bank_balance(&vest_addr, vec![coin(10, denom.clone())]);
let _ = app.init_bank_balance(&vest_addr, vec![coin(10, denom.clone())]);

let owner_balance = |app: &App<Empty>| {
app.wrap()
Expand Down Expand Up @@ -705,7 +768,7 @@ mod tests {
let vest_addr = instantiate_vest(&mut app, payments);

// Fund vest contract
app.init_bank_balance(&vest_addr, vec![coin(3, denom.clone())]);
let _ = app.init_bank_balance(&vest_addr, vec![coin(3, denom.clone())]);
fund_vest_contract(
&mut app,
vest_addr.clone(),
Expand Down
3 changes: 3 additions & 0 deletions src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub schedule: Vec<Payment>,
pub admin_address: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand All @@ -22,6 +23,8 @@ pub struct Payment {
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Pay {},
HaltPayments { recipient: String },
StartPayments { recipient: String },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand Down
5 changes: 4 additions & 1 deletion src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::msg::Payment;
use cosmwasm_std::{StdResult, Storage};
use cosmwasm_std::{Addr, Empty, StdResult, Storage};
use cw_storage_plus::{Item, Map, U64Key};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand All @@ -22,3 +22,6 @@ pub fn next_id(store: &mut dyn Storage) -> StdResult<u64> {
PAYMENT_COUNT.save(store, &id)?;
Ok(id)
}

pub const ADMIN_ADDRESS: Item<Addr> = Item::new("admin_address");
pub const HALTED_PAYEES: Map<Addr, Empty> = Map::new("halted_payees");