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

Add a whitelist #18

Merged
merged 11 commits into from
Apr 22, 2024
104 changes: 96 additions & 8 deletions contracts/atom_wars/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg};
use crate::query::{QueryMsg, RoundProposalsResponse, UserLockupsResponse};
use crate::state::{
Constants, LockEntry, Proposal, Tranche, Vote, CONSTANTS, LOCKS_MAP, LOCK_ID, PROPOSAL_MAP,
PROPS_BY_SCORE, PROP_ID, TOTAL_POWER_VOTING, TRANCHE_MAP, VOTE_MAP,
Constants, CovenantParams, LockEntry, Proposal, Tranche, Vote, CONSTANTS, LOCKS_MAP, LOCK_ID,
PROPOSAL_MAP, PROPS_BY_SCORE, PROP_ID, TOTAL_POWER_VOTING, TRANCHE_MAP, VOTE_MAP, WHITELIST,
WHITELIST_ADMINS,
};

pub const ONE_MONTH_IN_NANO_SECONDS: u64 = 2629746000000000; // 365 days / 12
Expand Down Expand Up @@ -48,6 +49,9 @@ pub fn instantiate(
LOCK_ID.save(deps.storage, &0)?;
PROP_ID.save(deps.storage, &0)?;

WHITELIST_ADMINS.save(deps.storage, &msg.whitelist_admins)?;
WHITELIST.save(deps.storage, &msg.initial_whitelist)?;

// For each tranche, create a tranche in the TRANCHE_MAP and set the total power to 0
let mut tranche_ids = std::collections::HashSet::new();

Expand Down Expand Up @@ -84,9 +88,14 @@ pub fn execute(
tranche_id,
proposal_id,
} => vote(deps, env, info, tranche_id, proposal_id),
// ExecuteMsg::ExecuteProposal { proposal_id } => {
// execute_proposal(deps, env, info, proposal_id)
// }
ExecuteMsg::AddToWhitelist { covenant_params } => {
add_to_whitelist(deps, env, info, covenant_params)
}
ExecuteMsg::RemoveFromWhitelist { covenant_params } => {
remove_from_whitelist(deps, env, info, covenant_params)
} // ExecuteMsg::ExecuteProposal { proposal_id } => {
// execute_proposal(deps, env, info, proposal_id)
// }
}
}

Expand Down Expand Up @@ -185,7 +194,7 @@ fn unlock_tokens(deps: DepsMut, env: Env, info: MessageInfo) -> Result<Response,
Ok(response)
}

fn validate_covenant_params(_covenant_params: String) -> Result<(), ContractError> {
fn validate_covenant_params(_covenant_params: CovenantParams) -> Result<(), ContractError> {
// Validate covenant_params
Ok(())
}
Expand All @@ -198,7 +207,7 @@ fn create_proposal(
deps: DepsMut,
env: Env,
tranche_id: u64,
covenant_params: String,
covenant_params: CovenantParams,
) -> Result<Response, ContractError> {
validate_covenant_params(covenant_params.clone())?;
TRANCHE_MAP.load(deps.storage, tranche_id)?;
Expand Down Expand Up @@ -413,6 +422,61 @@ fn _do_covenant_stuff(
Ok(Response::new().add_attribute("action", "do_covenant_stuff"))
}

// Adds a new covenant target to the whitelist.
fn add_to_whitelist(
deps: DepsMut,
_env: Env,
info: MessageInfo,
covenant_params: CovenantParams,
) -> Result<Response, ContractError> {
// Validate that the sender is a whitelist admin
let whitelist_admins = WHITELIST_ADMINS.load(deps.storage)?;
if !whitelist_admins.contains(&info.sender) {
return Err(ContractError::Unauthorized {});
}

// Validate covenant_params
validate_covenant_params(covenant_params.clone())?;

// Add covenant_params to whitelist
let mut whitelist = WHITELIST.load(deps.storage)?;

// return an error if the covenant_params is already in the whitelist
if whitelist.contains(&covenant_params) {
return Err(ContractError::Std(StdError::generic_err(
"Covenant params already in whitelist",
)));
}

whitelist.push(covenant_params.clone());
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
WHITELIST.save(deps.storage, &whitelist)?;

Ok(Response::new().add_attribute("action", "add_to_whitelist"))
}

fn remove_from_whitelist(
deps: DepsMut,
_env: Env,
info: MessageInfo,
covenant_params: CovenantParams,
) -> Result<Response, ContractError> {
// Validate that the sender is a whitelist admin
let whitelist_admins = WHITELIST_ADMINS.load(deps.storage)?;
if !whitelist_admins.contains(&info.sender) {
return Err(ContractError::Unauthorized {});
}

// Validate covenant_params
validate_covenant_params(covenant_params.clone())?;

// Remove covenant_params from whitelist
let mut whitelist = WHITELIST.load(deps.storage)?;
whitelist.retain(|cp| cp != &covenant_params);
WHITELIST.save(deps.storage, &whitelist)?;

Ok(Response::new().add_attribute("action", "remove_from_whitelist"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
Expand Down Expand Up @@ -452,6 +516,8 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
tranche_id,
number_of_proposals,
)?),
QueryMsg::Whitelist {} => to_json_binary(&query_whitelist(deps)?),
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved
QueryMsg::WhitelistAdmins {} => to_json_binary(&query_whitelist_admins(deps)?),
}
}

Expand Down Expand Up @@ -551,10 +617,24 @@ pub fn query_top_n_proposals(
return Err(StdError::generic_err("Tranche does not exist"));
}

// Iterate through PROPS_BY_SCORE to find the top num props
// load the whitelist
let whitelist = WHITELIST.load(deps.storage)?;

// Iterate through PROPS_BY_SCORE to find the top num props, while ignoring
// any props that are not on the whitelist
let top_prop_ids: Vec<u64> = PROPS_BY_SCORE
.sub_prefix((round_id, tranche_id))
.range(deps.storage, None, None, Order::Descending)
// filter out any props that are not on the whitelist
.filter(|x| match x {
Ok((_, prop_id)) => {
let prop = PROPOSAL_MAP
.load(deps.storage, (round_id, tranche_id, *prop_id))
.unwrap();
whitelist.contains(&prop.covenant_params)
}
Err(e) => false,
})
.take(num)
.map(|x| match x {
Ok((_, prop_id)) => prop_id,
Expand Down Expand Up @@ -608,6 +688,14 @@ fn query_user_lockups(
.collect()
}

fn query_whitelist(deps: Deps) -> StdResult<Vec<CovenantParams>> {
WHITELIST.load(deps.storage)
}

fn query_whitelist_admins(deps: Deps) -> StdResult<Vec<Addr>> {
WHITELIST_ADMINS.load(deps.storage)
}

// Computes the current round_id by taking contract_start_time and dividing the time since
// by the round_length.
pub fn compute_current_round_id(deps: Deps, env: Env) -> StdResult<u64> {
Expand Down
3 changes: 3 additions & 0 deletions contracts/atom_wars/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ use thiserror::Error;
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),

#[error("Unauthorized")]
Unauthorized {},
}
14 changes: 11 additions & 3 deletions contracts/atom_wars/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use cosmwasm_std::{Timestamp, Uint128};
use cosmwasm_std::{Addr, Timestamp, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::state::Tranche;
use crate::state::{CovenantParams, Tranche};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
Expand All @@ -11,6 +11,8 @@ pub struct InstantiateMsg {
pub total_pool: Uint128,
pub tranches: Vec<Tranche>,
pub first_round_start: Timestamp,
pub whitelist_admins: Vec<Addr>,
pub initial_whitelist: Vec<CovenantParams>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand All @@ -22,11 +24,17 @@ pub enum ExecuteMsg {
UnlockTokens {},
CreateProposal {
tranche_id: u64,
covenant_params: String,
covenant_params: CovenantParams,
},
Vote {
tranche_id: u64,
proposal_id: u64,
},
AddToWhitelist {
covenant_params: CovenantParams,
},
RemoveFromWhitelist {
covenant_params: CovenantParams,
},
// ExecuteProposal { proposal_id: u64 },
}
2 changes: 2 additions & 0 deletions contracts/atom_wars/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub enum QueryMsg {
tranche_id: u64,
number_of_proposals: usize,
},
Whitelist {},
WhitelistAdmins {},
}

#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
Expand Down
22 changes: 21 additions & 1 deletion contracts/atom_wars/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,24 @@ pub struct Proposal {
pub round_id: u64,
pub tranche_id: u64,
pub proposal_id: u64,
pub covenant_params: String,
pub covenant_params: CovenantParams,
pub executed: bool, // TODO: maybe remove in the future
pub power: Uint128,
pub percentage: Uint128,
}

#[cw_serde]
pub struct CovenantParams {
// identifies the pool in which to deploy the funds
pub pool_id: String,

// Identifies the channel to the chain on which the funds should be deployed
pub outgoing_channel_id: String,

// Another identifier to check the destination of the funds, e.g. Astroport, Osmosis, etc.
pub funding_destination_name: String,
}

// VOTE_MAP: key(round_id, tranche_id, sender_addr) -> Vote {
// prop_id: u64,
// power: Uint128,
Expand Down Expand Up @@ -77,3 +89,11 @@ pub struct Tranche {
pub tranche_id: u64,
pub metadata: String,
}

// The initial whitelist is set upon contract instantiation.
// It can be updated by anyone on the WHITELIST_ADMINS list
// via the update_whitelist message.
pub const WHITELIST: Item<Vec<CovenantParams>> = Item::new("whitelist");

// Every address in this list can manage the whitelist.
pub const WHITELIST_ADMINS: Item<Vec<Addr>> = Item::new("whitelist_admins");
43 changes: 27 additions & 16 deletions contracts/atom_wars/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ pub fn get_default_instantiate_msg() -> InstantiateMsg {
metadata: "tranche 1".to_string(),
}],
first_round_start: mock_env().block.time,
initial_whitelist: vec![get_default_covenant_params()],
whitelist_admins: vec![],
}
}

pub fn get_default_covenant_params() -> crate::state::CovenantParams {
crate::state::CovenantParams {
pool_id: "pool_id".to_string(),
outgoing_channel_id: "outgoing_channel_id".to_string(),
funding_destination_name: "funding_destination_name".to_string(),
}
}

Expand Down Expand Up @@ -171,18 +181,22 @@ fn create_proposal_basic_test() {
let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg.clone());
assert!(res.is_ok());

let covenant_params_1 = "first proposal";
let covenant_params_1 = get_default_covenant_params();
let msg1 = ExecuteMsg::CreateProposal {
tranche_id: 1,
covenant_params: covenant_params_1.to_string(),
covenant_params: covenant_params_1.clone(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg1.clone());
assert!(res.is_ok());

let covenant_params_2 = "second proposal";
let mut covenant_params_2 = get_default_covenant_params().clone();
covenant_params_2.pool_id = "pool_id_2".to_string();
covenant_params_2.outgoing_channel_id = "outgoing_channel_id_2".to_string();
covenant_params_2.funding_destination_name = "funding_destination_name_2".to_string();

let msg2 = ExecuteMsg::CreateProposal {
tranche_id: 1,
covenant_params: covenant_params_2.to_string(),
covenant_params: covenant_params_2.clone(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg2.clone());
assert!(res.is_ok());
Expand Down Expand Up @@ -228,7 +242,7 @@ fn vote_basic_test() {
// create a new proposal
let msg = ExecuteMsg::CreateProposal {
tranche_id: 1,
covenant_params: "proposal".to_string(),
covenant_params: get_default_covenant_params(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
assert!(res.is_ok());
Expand Down Expand Up @@ -274,15 +288,15 @@ fn multi_tranches_test() {
let covenant_params_1 = "first proposal";
let msg1 = ExecuteMsg::CreateProposal {
tranche_id: 1,
covenant_params: covenant_params_1.to_string(),
covenant_params: get_default_covenant_params(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg1.clone());
assert!(res.is_ok());

let covenant_params_2 = "second proposal";
let msg2 = ExecuteMsg::CreateProposal {
tranche_id: 1,
covenant_params: covenant_params_2.to_string(),
covenant_params: get_default_covenant_params(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg2.clone());
assert!(res.is_ok());
Expand All @@ -291,15 +305,15 @@ fn multi_tranches_test() {
let covenant_params_3 = "third proposal";
let msg3 = ExecuteMsg::CreateProposal {
tranche_id: 2,
covenant_params: covenant_params_3.to_string(),
covenant_params: get_default_covenant_params(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg3.clone());
assert!(res.is_ok());

let covenant_params_4 = "fourth proposal";
let msg4 = ExecuteMsg::CreateProposal {
tranche_id: 2,
covenant_params: covenant_params_4.to_string(),
covenant_params: get_default_covenant_params(),
};
let res = execute(deps.as_mut(), env.clone(), info.clone(), msg4.clone());
assert!(res.is_ok());
Expand Down Expand Up @@ -460,13 +474,10 @@ fn test_round_id_computation() {
for (contract_start_time, round_length, current_time, expected_round_id) in test_cases {
// instantiate the contract
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
denom: STATOM.to_string(),
round_length,
total_pool: Uint128::zero(),
tranches: vec![],
first_round_start: Timestamp::from_nanos(contract_start_time),
};
let mut msg = get_default_instantiate_msg();
msg.round_length = round_length;
msg.first_round_start = Timestamp::from_nanos(contract_start_time);

let mut env = mock_env();
env.block.time = Timestamp::from_nanos(contract_start_time);
let info = mock_info("addr0000", &[]);
Expand Down
Loading