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 instantiate allowlist to cw-payroll-factory #884

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion contracts/external/cw-payroll-factory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name ="cw-payroll-factory"
name = "cw-payroll-factory"
authors = ["Jake Hartnell"]
description = "A CosmWasm factory contract for instantiating a payroll contract."
edition = { workspace = true }
Expand Down Expand Up @@ -27,6 +27,7 @@ cw20 = { workspace = true }
thiserror = { workspace = true }
cw-vesting = { workspace = true, features = ["library"] }
cw-utils = { workspace = true }
cw-paginate-storage = { workspace = true }

[dev-dependencies]
cw-multi-test = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
"vesting_code_id"
],
"properties": {
"instantiate_allowlist": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"owner": {
"type": [
"string",
Expand Down Expand Up @@ -91,6 +100,40 @@
},
"additionalProperties": false
},
{
"description": "Callable only by the current owner. Updates the addresses that are allowed to instantiate vesting contracts.",
"type": "object",
"required": [
"update_instantiate_allowlist"
],
"properties": {
"update_instantiate_allowlist": {
"type": "object",
"properties": {
"to_add": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"to_remove": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.",
"type": "object",
Expand Down Expand Up @@ -636,6 +679,36 @@
}
},
"additionalProperties": false
},
{
"description": "Returns the allowlist Addresses allowed to instantiate vesting contracts",
"type": "object",
"required": [
"instantiate_allowlist"
],
"properties": {
"instantiate_allowlist": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
]
},
Expand All @@ -649,6 +722,23 @@
"format": "uint64",
"minimum": 0.0
},
"instantiate_allowlist": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Nullable_Array_of_Addr",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Addr"
},
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
},
"list_vesting_contracts": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Array_of_VestingContract",
Expand Down
114 changes: 98 additions & 16 deletions contracts/external/cw-payroll-factory/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
from_json, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Reply,
Response, StdResult, SubMsg, WasmMsg,
from_json, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order,
Reply, Response, StdResult, SubMsg, WasmMsg,
};
use cosmwasm_std::{Addr, Coin};

Expand All @@ -19,8 +19,11 @@ use cw_vesting::msg::{
use cw_vesting::vesting::Vest;

use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, ReceiveMsg};
use crate::state::{vesting_contracts, VestingContract, TMP_INSTANTIATOR_INFO, VESTING_CODE_ID};
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, ReceiveMsg};
use crate::state::{
vesting_contracts, VestingContract, INSTANTIATE_ALLOWLIST, TMP_INSTANTIATOR_INFO,
VESTING_CODE_ID,
};

pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-payroll-factory";
pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand All @@ -31,16 +34,32 @@ pub const MAX_LIMIT: u32 = 50;
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
cw_ownable::initialize_owner(deps.storage, deps.api, msg.owner.as_deref())?;
let ownership = cw_ownable::initialize_owner(deps.storage, deps.api, msg.owner.as_deref())?;
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
VESTING_CODE_ID.save(deps.storage, &msg.vesting_code_id)?;

let mut msgs = vec![];

if let Some(allowlist) = msg.instantiate_allowlist {
msgs.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_json_binary(&ExecuteMsg::UpdateInstantiateAllowlist {
to_add: Some(allowlist),
to_remove: None,
})?,
funds: vec![],
}))
}

Ok(Response::new()
.add_messages(msgs)
.add_attribute("method", "instantiate")
.add_attribute("creator", info.sender))
.add_attribute("creator", info.sender)
.add_attributes(ownership.into_attributes()))
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -60,9 +79,54 @@ pub fn execute(
ExecuteMsg::UpdateCodeId { vesting_code_id } => {
execute_update_code_id(deps, info, vesting_code_id)
}
ExecuteMsg::UpdateInstantiateAllowlist { to_add, to_remove } => {
execute_set_instantiate_allowlist(deps, env, info, to_add, to_remove)
}
}
}

pub fn execute_set_instantiate_allowlist(
deps: DepsMut,
env: Env,
info: MessageInfo,
to_add: Option<Vec<String>>,
to_remove: Option<Vec<String>>,
) -> Result<Response, ContractError> {
if info.sender != env.contract.address {
cw_ownable::assert_owner(deps.storage, &info.sender)?;
}

// Add new addresses
if let Some(add_list) = to_add.as_ref() {
for addr_str in add_list {
let addr = deps.api.addr_validate(addr_str)?;

if !INSTANTIATE_ALLOWLIST.has(deps.storage, &addr) {
INSTANTIATE_ALLOWLIST.save(deps.storage, &addr, &Empty {})?;
}
}
}

// Remove addresses
if let Some(remove_list) = to_remove.as_ref() {
for addr_str in remove_list {
let addr = deps.api.addr_validate(addr_str)?;
INSTANTIATE_ALLOWLIST.remove(deps.storage, &addr);
}
}

Ok(Response::new()
.add_attribute("action", "set_instantiate_allowlist")
.add_attribute(
"added",
to_add.map_or_else(|| "none".to_string(), |v| v.join(", ")),
)
.add_attribute(
"removed",
to_remove.map_or_else(|| "none".to_string(), |v| v.join(", ")),
))
}

pub fn execute_receive_cw20(
_env: Env,
deps: DepsMut,
Expand Down Expand Up @@ -121,15 +185,14 @@ pub fn instantiate_contract(
instantiate_msg: PayrollInstantiateMsg,
label: String,
) -> Result<Response, ContractError> {
// Check sender is contract owner if set
let ownership = cw_ownable::get_ownership(deps.storage)?;
if ownership
.owner
.as_ref()
.map_or(false, |owner| *owner != sender)
{
return Err(ContractError::Unauthorized {});
}
// Check sender is contract owner if set - or an allowlisted address
cw_ownable::assert_owner(deps.storage, &sender).or_else(|e| {
if INSTANTIATE_ALLOWLIST.has(deps.storage, &sender) {
Ok(())
} else {
Err(ContractError::Ownable(e))
}
})?;

let code_id = VESTING_CODE_ID.load(deps.storage)?;

Expand Down Expand Up @@ -291,6 +354,19 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
}
QueryMsg::Ownership {} => to_json_binary(&cw_ownable::get_ownership(deps.storage)?),
QueryMsg::CodeId {} => to_json_binary(&VESTING_CODE_ID.load(deps.storage)?),
QueryMsg::InstantiateAllowlist { start_after, limit } => {
let start_after = start_after
.map(|x| deps.api.addr_validate(&x))
.transpose()?;

to_json_binary(&cw_paginate_storage::paginate_map(
deps,
&INSTANTIATE_ALLOWLIST,
start_after.as_ref(),
limit,
Order::Ascending,
)?)
}
}
}

Expand Down Expand Up @@ -346,3 +422,9 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractE
_ => Err(ContractError::UnknownReplyId { id: msg.id }),
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Response::default())
}
3 changes: 0 additions & 3 deletions contracts/external/cw-payroll-factory/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ pub enum ContractError {
#[error("{0}")]
PaymentError(#[from] PaymentError),

#[error("Unauthorized")]
Unauthorized {},

#[error("{0}")]
ParseReplyError(#[from] ParseReplyError),

Expand Down
20 changes: 20 additions & 0 deletions contracts/external/cw-payroll-factory/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use cw_vesting::msg::InstantiateMsg as PayrollInstantiateMsg;
pub struct InstantiateMsg {
pub owner: Option<String>,
pub vesting_code_id: u64,
pub instantiate_allowlist: Option<Vec<String>>,
}

#[cw_ownable_execute]
Expand All @@ -23,6 +24,12 @@ pub enum ExecuteMsg {
/// Callable only by the current owner. Updates the code ID used
/// while instantiating vesting contracts.
UpdateCodeId { vesting_code_id: u64 },

/// Callable only by the current owner. Updates the addresses that are allowed to instantiate vesting contracts.
UpdateInstantiateAllowlist {
to_add: Option<Vec<String>>,
to_remove: Option<Vec<String>>,
},
}

// Receiver setup
Expand Down Expand Up @@ -85,4 +92,17 @@ pub enum QueryMsg {
/// Returns the code ID currently being used to instantiate vesting contracts.
#[returns(::std::primitive::u64)]
CodeId {},

/// Returns the allowlist
/// Addresses allowed to instantiate vesting contracts
#[returns(Option<Vec<::cosmwasm_std::Addr>>)]
InstantiateAllowlist {
start_after: Option<String>,
limit: Option<u32>,
},
}

#[cw_serde]
pub enum MigrateMsg {
FromCompatible {},
}
5 changes: 3 additions & 2 deletions contracts/external/cw-payroll-factory/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex};
use cosmwasm_std::{Addr, Empty};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex};

/// Temporarily holds the address of the instantiator for use in submessages
pub const TMP_INSTANTIATOR_INFO: Item<Addr> = Item::new("tmp_instantiator_info");
pub const VESTING_CODE_ID: Item<u64> = Item::new("pci");
pub const INSTANTIATE_ALLOWLIST: Map<&Addr, Empty> = Map::new("instantiate_allowlist");

#[cw_serde]
pub struct VestingContract {
Expand Down
Loading
Loading