diff --git a/Cargo.lock b/Cargo.lock index 7797b3b..362fcc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,21 @@ dependencies = [ "utils", ] +[[package]] +name = "compliance-whitelist-wrapper" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "cw2", + "schemars", + "serde", + "thiserror", + "utils", +] + [[package]] name = "const-oid" version = "0.9.6" diff --git a/contracts/compliance-whitelist-wrapper/Cargo.toml b/contracts/compliance-whitelist-wrapper/Cargo.toml new file mode 100644 index 0000000..7dddfd5 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "compliance-whitelist-wrapper" +version = "0.1.0" +authors = ["Thales Zirbel "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + + +[features] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.15.0 +""" + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +schemars = "0.8.16" +serde = { version = "1.0.197", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.58" } +utils = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } diff --git a/contracts/compliance-whitelist-wrapper/README.md b/contracts/compliance-whitelist-wrapper/README.md new file mode 100644 index 0000000..5027c7c --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/README.md @@ -0,0 +1,5 @@ +# Compliance Whitelist Wrapper + +This contract wraps other compliance contracts, filtering what is sent to them. If it finds an address that is in the whitelist, it will substitute it with None before forwarding it to the wrapped compliance module. + +Only addresses with the OwnerRole ComplianceManager can add or remove addresses from the whitelist. Each compliance contract should have its own wrapper. diff --git a/contracts/compliance-whitelist-wrapper/src/bin/schema.rs b/contracts/compliance-whitelist-wrapper/src/bin/schema.rs new file mode 100644 index 0000000..7e24478 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/bin/schema.rs @@ -0,0 +1,11 @@ +use compliance_whitelist_wrapper::msg::{ExecuteMsg, InstantiateMsg}; +use cosmwasm_schema::write_api; +use utils::compliance::QueryMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/compliance-whitelist-wrapper/src/contract.rs b/contracts/compliance-whitelist-wrapper/src/contract.rs new file mode 100644 index 0000000..4fcba23 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/contract.rs @@ -0,0 +1,379 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::{COMPLIANCE_MODULE_ADDRESS, OWNER_ROLES_ADDRESS, WHITELISTED_ADDRESSES}; +use utils::owner_roles::OwnerRole; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:compliance"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Instantiate a new compliance contract +/// +/// # Arguments +/// +/// * `deps` - Mutable dependencies +/// * `_env` - The environment info (unused) +/// * `info` - Message info +/// * `msg` - Instantiate message containing the owner roles address and compliance module address +/// +/// # Returns +/// +/// * `Result` +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + OWNER_ROLES_ADDRESS.save(deps.storage, &msg.owner_roles_address)?; + COMPLIANCE_MODULE_ADDRESS.save(deps.storage, &msg.module_address)?; + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender)) +} + +/// Execute function for the compliance contract +/// +/// # Arguments +/// +/// * `deps` - Mutable dependencies +/// * `_env` - The environment info (unused) +/// * `info` - Message info +/// * `msg` - Execute message +/// +/// # Returns +/// +/// * `Result` +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + // checking with the owner role contract to ensure only authorized personnel + // with a role of ComplianceManager are allowed to execute the functions + execute::check_role(deps.as_ref(), info.sender, OwnerRole::ComplianceManager)?; + + match msg { + ExecuteMsg::ChangeComplianceModule { module_address } => { + execute::change_compliance_module(deps, module_address) + } + ExecuteMsg::AddAddressToWhitelist { address } => { + execute::add_address_to_whitelist(deps, address) + } + ExecuteMsg::RemoveAddressFromWhitelist { address } => { + execute::remove_address_from_whitelist(deps, address) + } + } +} + +pub mod execute { + use crate::state::WHITELISTED_ADDRESSES; + + use super::*; + use cosmwasm_std::{to_json_binary, Addr, QueryRequest, WasmQuery}; + use utils::owner_roles::{IsOwnerResponse, QueryMsg}; + + /// Check if address has a specific owner role + pub fn check_role(deps: Deps, owner: Addr, role: OwnerRole) -> Result<(), ContractError> { + let owner_roles = OWNER_ROLES_ADDRESS.load(deps.storage)?; + let msg = QueryMsg::IsOwner { role, owner }; + + let query = QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: owner_roles.to_string(), + msg: to_json_binary(&msg)?, + }); + + let response: IsOwnerResponse = deps.querier.query(&query)?; + if !response.is_owner { + return Err(ContractError::Unauthorized {}); + } + Ok(()) + } + + /// change_compliance_module changes the compliance module address utilized + pub fn change_compliance_module( + deps: DepsMut, + module_address: Addr, + ) -> Result { + COMPLIANCE_MODULE_ADDRESS.save(deps.storage, &module_address)?; + + Ok(Response::new() + .add_attribute("action", "change_compliance_module") + .add_attribute("module_address", module_address.to_string())) + } + + /// Adds an address to the whitelist + pub fn add_address_to_whitelist( + deps: DepsMut, + address: Addr, + ) -> Result { + WHITELISTED_ADDRESSES.save(deps.storage, address.clone(), &true)?; + Ok(Response::new().add_attribute("action", "add_address_to_whitelist")) + } + + /// Removes an address from the whitelist + pub fn remove_address_from_whitelist( + deps: DepsMut, + address: Addr, + ) -> Result { + WHITELISTED_ADDRESSES.remove(deps.storage, address); + Ok(Response::new().add_attribute("action", "remove_address_from_whitelist")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: utils::compliance::QueryMsg) -> StdResult { + match msg { + utils::compliance::QueryMsg::CheckTokenCompliance { + token_address, + from, + to, + amount, + } => to_json_binary(&query::check_compliance( + deps, + token_address, + from, + to, + amount, + )?), + } +} + +/// Check if a given address is whitelisted +fn is_whitelisted(deps: Deps, address: Option) -> bool { + match address { + None => true, + Some(addr) => WHITELISTED_ADDRESSES.load(deps.storage, addr).is_ok(), + } +} + +pub mod query { + use super::*; + use cosmwasm_std::{to_json_binary, Addr, QueryRequest, Uint128, WasmQuery}; + + /// Check compliance for a token transfer + pub fn check_compliance( + deps: Deps, + token_address: Addr, + from: Option, + to: Option, + amount: Option, + ) -> StdResult { + // Get compliance module and whitelisted addresses + let module_address = COMPLIANCE_MODULE_ADDRESS.load(deps.storage)?; + + // Replace whitelisted addrs with None + let from = if is_whitelisted(deps, from.clone()) { + None + } else { + from + }; + let to = if is_whitelisted(deps, to.clone()) { + None + } else { + to + }; + + // Check compliance with wrapped module + let msg = utils::compliance::QueryMsg::CheckTokenCompliance { + token_address: token_address.clone(), + from: from.clone(), + to: to.clone(), + amount, + }; + + let query = QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: module_address.to_string(), + msg: to_json_binary(&msg)?, + }); + let is_compliant: bool = deps.querier.query(&query)?; + if !is_compliant { + return Ok(false); + } + + Ok(true) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; + use cosmwasm_std::{from_json, Addr, ContractResult, SystemResult, Uint128}; + use utils::owner_roles::{IsOwnerResponse, OwnerRole}; + + // Helper function to instantiate the contract + fn setup_contract(deps: DepsMut) { + let msg = InstantiateMsg { + owner_roles_address: Addr::unchecked("owner_roles"), + module_address: Addr::unchecked("compliance_module"), + }; + let info = message_info(&Addr::unchecked("creator"), &[]); + let res = instantiate(deps, mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + } + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + setup_contract(deps.as_mut()); + + // Check that the owner_roles_address was properly set + let owner_roles = OWNER_ROLES_ADDRESS.load(deps.as_ref().storage).unwrap(); + assert_eq!(owner_roles, Addr::unchecked("owner_roles")); + + // Check that the module_address was properly set + let compliance_module = COMPLIANCE_MODULE_ADDRESS + .load(deps.as_ref().storage) + .unwrap(); + assert_eq!(compliance_module, Addr::unchecked("compliance_module")); + } + + #[test] + fn add_compliance_module() { + let mut deps = mock_dependencies(); + setup_contract(deps.as_mut()); + + // Mock the owner roles contract query + deps.querier.update_wasm(|query| match query { + cosmwasm_std::WasmQuery::Smart { msg, .. } => { + let parsed: utils::owner_roles::QueryMsg = from_json(msg).unwrap(); + match parsed { + utils::owner_roles::QueryMsg::IsOwner { role, .. } => { + if role == OwnerRole::ComplianceManager { + SystemResult::Ok(ContractResult::Ok( + to_json_binary(&IsOwnerResponse { + is_owner: true, + role: OwnerRole::ComplianceManager, + }) + .unwrap(), + )) + } else { + panic!("Unexpected role query") + } + } + } + } + _ => panic!("Unexpected query type"), + }); + let info = message_info(&Addr::unchecked("admin"), &[]); + let msg = ExecuteMsg::ChangeComplianceModule { + module_address: Addr::unchecked("module"), + }; + + let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(2, res.attributes.len()); + } + + #[test] + fn add_and_remove_from_whitelist() { + let mut deps = mock_dependencies(); + setup_contract(deps.as_mut()); + + // Mock the owner roles contract query + deps.querier.update_wasm(|query| match query { + cosmwasm_std::WasmQuery::Smart { msg, .. } => { + let parsed: utils::owner_roles::QueryMsg = from_json(msg).unwrap(); + match parsed { + utils::owner_roles::QueryMsg::IsOwner { role, .. } => { + if role == OwnerRole::ComplianceManager { + SystemResult::Ok(ContractResult::Ok( + to_json_binary(&IsOwnerResponse { + is_owner: true, + role: OwnerRole::ComplianceManager, + }) + .unwrap(), + )) + } else { + panic!("Unexpected role query") + } + } + } + } + _ => panic!("Unexpected query type"), + }); + + let info = message_info(&Addr::unchecked("admin"), &[]); + let add_msg = ExecuteMsg::AddAddressToWhitelist { + address: Addr::unchecked("address"), + }; + let res = execute(deps.as_mut(), mock_env(), info.clone(), add_msg).unwrap(); + assert_eq!(1, res.attributes.len()); + + // Now remove the module + let remove_msg = ExecuteMsg::RemoveAddressFromWhitelist { + address: Addr::unchecked("address"), + }; + let res = execute(deps.as_mut(), mock_env(), info, remove_msg).unwrap(); + assert_eq!(1, res.attributes.len()); + } + + #[test] + fn check_compliance() { + let mut deps = mock_dependencies(); + setup_contract(deps.as_mut()); + + // Mock the owner roles contract query + deps.querier.update_wasm(|query| match query { + cosmwasm_std::WasmQuery::Smart { msg, .. } => { + let parsed: utils::owner_roles::QueryMsg = from_json(msg).unwrap(); + match parsed { + utils::owner_roles::QueryMsg::IsOwner { role, .. } => { + if role == OwnerRole::ComplianceManager { + SystemResult::Ok(ContractResult::Ok( + to_json_binary(&IsOwnerResponse { + is_owner: true, + role: OwnerRole::ComplianceManager, + }) + .unwrap(), + )) + } else { + panic!("Unexpected role query") + } + } + } + } + _ => panic!("Unexpected query type"), + }); + + // Mock the module query + deps.querier.update_wasm(|query| match query { + cosmwasm_std::WasmQuery::Smart { msg, .. } => { + let parsed: utils::compliance::QueryMsg = from_json(msg).unwrap(); + match parsed { + utils::compliance::QueryMsg::CheckTokenCompliance { + token_address: _, + from: _, + to: _, + amount: _, + } => SystemResult::Ok(ContractResult::Ok(to_json_binary(&true).unwrap())), + } + } + _ => panic!("Unexpected query type"), + }); + + // Check compliance + let msg = utils::compliance::QueryMsg::CheckTokenCompliance { + token_address: Addr::unchecked("token"), + from: Some(Addr::unchecked("sender")), + to: Some(Addr::unchecked("receiver")), + amount: Some(Uint128::new(100)), + }; + + let res = query(deps.as_ref(), mock_env(), msg).unwrap(); + let is_compliant: bool = from_json(res).unwrap(); + assert!(is_compliant); + } +} diff --git a/contracts/compliance-whitelist-wrapper/src/error.rs b/contracts/compliance-whitelist-wrapper/src/error.rs new file mode 100644 index 0000000..4b4168b --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("ComplianceNotFound")] + ComplianceNotFound {}, +} diff --git a/contracts/compliance-whitelist-wrapper/src/helpers.rs b/contracts/compliance-whitelist-wrapper/src/helpers.rs new file mode 100644 index 0000000..6ffc885 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/helpers.rs @@ -0,0 +1,27 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; + +use crate::msg::ExecuteMsg; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_json_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } +} diff --git a/contracts/compliance-whitelist-wrapper/src/lib.rs b/contracts/compliance-whitelist-wrapper/src/lib.rs new file mode 100644 index 0000000..233dbf5 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +mod error; +pub mod helpers; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/contracts/compliance-whitelist-wrapper/src/msg.rs b/contracts/compliance-whitelist-wrapper/src/msg.rs new file mode 100644 index 0000000..fd4410c --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/msg.rs @@ -0,0 +1,15 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; + +#[cw_serde] +pub struct InstantiateMsg { + pub owner_roles_address: Addr, + pub module_address: Addr, +} + +#[cw_serde] +pub enum ExecuteMsg { + ChangeComplianceModule { module_address: Addr }, + AddAddressToWhitelist { address: Addr }, + RemoveAddressFromWhitelist { address: Addr }, +} diff --git a/contracts/compliance-whitelist-wrapper/src/state.rs b/contracts/compliance-whitelist-wrapper/src/state.rs new file mode 100644 index 0000000..64db773 --- /dev/null +++ b/contracts/compliance-whitelist-wrapper/src/state.rs @@ -0,0 +1,6 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +pub const OWNER_ROLES_ADDRESS: Item = Item::new("owner_role_contract_address"); +pub const COMPLIANCE_MODULE_ADDRESS: Item = Item::new("compliance_module_address"); +pub const WHITELISTED_ADDRESSES: Map = Map::new("whitelisted_addresses"); diff --git a/scripts/common.py b/scripts/common.py index 55a0b09..61afa47 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -190,6 +190,131 @@ def execute_contract(contract_address, msg, from_key): # If all is fine we wait for the tx to be processed return check_tx_until_result(tx_hash) +# Create pair executes a kiichaind command to create a factory token as a pair +def create_pair(factory_address, msg, from_key): + # Build the command to execute the contract + cmd = [ + KIICHAIN, + "tx", + "wasm", + "execute", + factory_address, + json.dumps(msg), + "--from", + from_key, + ] + TXFLAG + + # Run the command + result, err = run_cmd(cmd) + if len(err.splitlines()) > 1 or "gas estimate" not in err.splitlines()[0]: + raise Exception(f"Failed to execute contract: {err}") + + # Get the result and the code + code = json.loads(result)["code"] + tx_hash = json.loads(result)["txhash"] + + # Check if the code was success + if code != 0: + raise Exception(f"Failed to execute contract: {result}") + + # If all is fine we wait for the tx to be processed + result = check_tx_until_result(tx_hash) + + # Get the pair address from the result + pair_address = next( + attr["value"] + for event in result["tx_response"]["events"] + for attr in event["attributes"] + if event["type"] == "wasm" and attr["key"] == "pair_contract_addr" + ) + + # Return the contract address + return pair_address + +def increase_allowance(cw_token_address, pair_address, key_name, amount): + """Increase allowance for the pair contract to spend CW20 tokens""" + allowance_msg = { + "increase_allowance": { + "spender": pair_address, + "amount": str(amount) + } + } + + cmd = [ + KIICHAIN, + "tx", + "wasm", + "execute", + cw_token_address, + json.dumps(allowance_msg), + "--from", + key_name, + ] + TXFLAG + + result, err = run_cmd(cmd) + if len(err.splitlines()) > 1 or "gas estimate" not in err.splitlines()[0]: + raise Exception(f"Failed to increase allowance: {err}") + + # Check transaction result + result_json = json.loads(result) + if result_json.get("code") != 0: + raise Exception(f"Failed to increase allowance: {result}") + + tx_hash = result_json["txhash"] + events = check_tx_until_result(tx_hash) + print("Allowance increased successfully") + return events + +def provide_liquidity(pair_address, cw_token_address, cw20_amount, native_amount, key_name): + """Provide liquidity to the pair""" + liquidity_msg = { + "provide_liquidity": { + "assets": [ + { + "info": { + "token": {"contract_addr": cw_token_address} + }, + "amount": str(cw20_amount) + }, + { + "info": { + "native_token": {"denom": "akii"} + }, + "amount": str(native_amount) + } + ], + "slippage_tolerance": "0.02", + "auto_stake": False + } + } + + cmd = [ + KIICHAIN, + "tx", + "wasm", + "execute", + pair_address, + json.dumps(liquidity_msg), + "--amount", + f"{native_amount}akii", + "--from", + key_name, + ] + TXFLAG + + result, err = run_cmd(cmd) + if len(err.splitlines()) > 1 or "gas estimate" not in err.splitlines()[0]: + raise Exception(f"Failed to provide liquidity: {err}") + + # Check transaction result + result_json = json.loads(result) + if result_json.get("code") != 0: + raise Exception(f"Failed to provide liquidity: {result}") + + tx_hash = result_json["txhash"] + events = check_tx_until_result(tx_hash) + print("Liquidity added successfully") + return events + # Query contract queries a contract with the given query message def query_contract(contract_address, query_msg): # Build the command to query the contract diff --git a/scripts/compliance_setup.py b/scripts/compliance_setup.py index 76d98fd..da03acd 100755 --- a/scripts/compliance_setup.py +++ b/scripts/compliance_setup.py @@ -2,7 +2,7 @@ import sys import config -from common import execute_contract, query_contract +from common import execute_contract ############################# # Import Core Variables # @@ -16,6 +16,36 @@ # Functions # ############# +def remove_old_compliances(token_address): + # For now we add the compliances we have to the registry we have + # Claims compliance + print(f"Removing old claim compliance from token {token_address}...") + execute_contract( + CONTRACTS["compliance_registry_address"], + { + "remove_compliance_module": { + "token_address": token_address, + "module_address": CONTRACTS["compliance_claims_address"], + } + }, + OWNER_KEY_NAME, + ) + print("Claim compliance removed") + + # Country compliance + print(f"Removing old country compliance from token {token_address}...") + execute_contract( + CONTRACTS["compliance_registry_address"], + { + "remove_compliance_module": { + "token_address": token_address, + "module_address": CONTRACTS["compliance_country_restriction_address"], + } + }, + OWNER_KEY_NAME, + ) + print("Country compliance removed") + def add_compliance_to_token(token_address): # For now we add the compliances we have to the registry we have # Claims compliance @@ -25,8 +55,8 @@ def add_compliance_to_token(token_address): { "add_compliance_module": { "token_address": token_address, - "module_address": CONTRACTS["compliance_claims_address"], - "module_name": "ClaimsCompliance" + "module_address": CONTRACTS["compliance_claims_wrapper_address"], + "module_name": "ClaimsComplianceWrapped" } }, OWNER_KEY_NAME, @@ -40,14 +70,51 @@ def add_compliance_to_token(token_address): { "add_compliance_module": { "token_address": token_address, - "module_address": CONTRACTS["compliance_country_restriction_address"], - "module_name": "ClaimsCompliance" + "module_address": CONTRACTS["compliance_country_wrapper_address"], + "module_name": "CountryComplianceWrapped" } }, OWNER_KEY_NAME, ) print("Country compliance added") +def add_compliance_claim_to_token(token_address): + print(f"Adding claim topic restriction to token {token_address}...") + execute_contract( + CONTRACTS["claim_topics_address"], + { + "add_claim_topic_for_token": { + "token_addr": token_address, + "topic": "1", + } + }, + OWNER_KEY_NAME, + ) + print("Restriction added") + +def whitelist_address(address): + print(f"Whitelisting address {address} on country compliance wrapper") + execute_contract( + CONTRACTS["compliance_country_wrapper_address"], + { + "add_address_to_whitelist": { + "address": address, + } + }, + OWNER_KEY_NAME, + ) + print(f"Whitelisting address {address} on claims compliance wrapper") + execute_contract( + CONTRACTS["compliance_claims_wrapper_address"], + { + "add_address_to_whitelist": { + "address": address, + } + }, + OWNER_KEY_NAME, + ) + + ######## # Call # ######## diff --git a/scripts/config.py b/scripts/config.py index ec36d5a..267a250 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -16,8 +16,12 @@ "compliance_registry_address": "kii1dpws8lmu2l4awdau6zhezxm97tshu427aulr2xwp7uarpd8jqu7srjz2gm", "compliance_claims_address": "kii12n3frtnx8mh2vpvzk7mqkr6yqkfe77339c7uakzqqa4pv46zdr9qt020h3", "compliance_country_restriction_address": "kii1k4j6gr75k23tvqjdw9zvtrdxh7pxtexy5pdk7xjmwf385msx75fsz470kz", + "compliance_claims_wrapper_address" : "kii1xlz8y3v0u0z428tvhdlqh930rmnafy84ecjc37gxacd02remdl8s00sh94", + "compliance_country_wrapper_address" : "kii125u5ufx0jfelud68zn40dyvk8gfsat24s7v322jwnxghjtve7rtqsqchja", "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", } +CW20_BASE_CODE_ID = "160" +FACTORY_ADDRESS="kii1ct5yx003l9h2fsahug0vpx7xec4k66he0try0fclxg59fd4aztys4mnds9" # Management TRUSTED_ISSUER_KEY_NAME = "trusted_issuer" diff --git a/scripts/deploy.py b/scripts/deploy.py index 265191e..b54ad3f 100755 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -125,6 +125,44 @@ f"Compliance Country Restriction contract instantiated at {compliance_country_restriction_address}" ) +# Compliance wrapper for country and claims +print("Deploying Compliance wrapper") +compliance_wrapper = store_contract( + "artifacts/compliance_wrapper.wasm", KEY_NAME +) +print( + f"Compliance wrapper stored with code ID {compliance_wrapper}" +) + +# Country wrapper +compliance_wrapper_country_init_msg = { + "module_address": compliance_country_restriction_address, + "owner_roles_address": owner_roles_address, +} +compliance_wrapper_country_address = instantiate_contract( + compliance_wrapper, + compliance_wrapper_country_init_msg, + "ComplianceCountryWrapper", + KEY_NAME, +) +print( + f"Compliance Country Wrapper contract instantiated at {compliance_wrapper_country_address}" +) +# Claims wrapper +compliance_wrapper_claims_init_msg = { + "module_address": compliance_claims_address, + "owner_roles_address": owner_roles_address, +} +compliance_wrapper_claims_address = instantiate_contract( + compliance_wrapper, + compliance_wrapper_claims_init_msg, + "ComplianceClaimsWrapper", + KEY_NAME, +) +print( + f"Compliance Claims Wrapper contract instantiated at {compliance_wrapper_claims_address}" +) + # CW20 base for test token print("Deploying CW20 Base contract...") cw20_base = store_contract("artifacts/cw20_base.wasm", KEY_NAME) diff --git a/scripts/new_trex.py b/scripts/new_trex.py new file mode 100755 index 0000000..e8e6b14 --- /dev/null +++ b/scripts/new_trex.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import config +import sys +from common import create_pair, increase_allowance, instantiate_contract, provide_liquidity +from compliance_setup import add_compliance_claim_to_token, add_compliance_to_token, whitelist_address + +############################# +# Import Core Variables # +############################# + +OWNER_KEY_NAME = config.OWNER_KEY_NAME +OWNER_KEY_ADDRESS = config.OWNER_KEY_ADDRESS +TRUSTED_ISSUER_KEY_NAME = config.TRUSTED_ISSUER_KEY_NAME +TRUSTED_ISSUER_KEY_ADDRESS = config.TRUSTED_ISSUER_KEY_ADDRESS +CONTRACTS = config.CONTRACTS +CW20_BASE_CODE_ID = config.CW20_BASE_CODE_ID +FACTORY_ADDRESS = config.FACTORY_ADDRESS + +############# +# Functions # +############# + +# Deploy trex creates a new instance of a trex contract with the default owner +def deploy_trex(): + print("Deploying trex") + cw20_init_msg = { + "token_info": { + "name": "Test Token", + "symbol": "TEST", + "decimals": 6, + "initial_balances": [{"address": OWNER_KEY_ADDRESS, "amount": "1000000"}], + }, + "registries": { + "compliance_address": CONTRACTS["compliance_registry_address"], + }, + } + trex_base_address = instantiate_contract( + CW20_BASE_CODE_ID, cw20_init_msg, "T-REX", OWNER_KEY_NAME + ) + print(f"Contract deployed with address: {trex_base_address}") + return trex_base_address + +# create liquidity creates a token pair associated with the TREX and gives it liquidity +def create_liquidity(token_address, cw20_amount, native_amount): + print("Creating pair to kii ") + pair_msg = { + "create_pair": { + "pair_type": { + "xyk": {} + }, + "asset_infos": [ + { + "native_token": { + "denom": "akii" + } + }, + { + "token": { + "contract_addr": token_address + } + } + ], + "init_params": "e30=" + } + } + pair_address = create_pair(FACTORY_ADDRESS, pair_msg, OWNER_KEY_NAME) + print(f"Pair created with address {pair_address}") + + print("Giving allowance to liquidity") + increase_allowance(token_address, pair_address, OWNER_KEY_NAME, cw20_amount) + + print("Providing liquidity to pair") + provide_liquidity(pair_address, token_address, cw20_amount, native_amount, OWNER_KEY_NAME) + return pair_address + +# setup trex deploys and sets up a pool and compliance for a trex +def setup_trex(): + print("Setting up new trex token") + # Deploy new instance of contract with our default owner + trex_address = deploy_trex() + + # Create pair and liquidity + # Please save this pair somewhere, it is important to do swaps in the future + cw20_amount = 1000000 + native_amount = 10000000000000 + pair_address = create_liquidity(trex_address, cw20_amount, native_amount) + + # Add compliance modules to token, using same ones we already have + # This means the same trusted issuer will be used + add_compliance_to_token(trex_address) + + # Whitelist the pair address so it can pass claims + whitelist_address(pair_address) + + # Adding claim to token, assuming default topic 1 + add_compliance_claim_to_token(trex_address) + +######## +# Call # +######## + +if __name__== "__main__": + setup_trex() diff --git a/scripts/user_setup.py b/scripts/user_setup.py index 56a7ad2..1d78dfe 100755 --- a/scripts/user_setup.py +++ b/scripts/user_setup.py @@ -108,6 +108,7 @@ def setup_user(user_name): ######## # Call # ######## + if __name__== "__main__": if len(sys.argv) > 1: name = sys.argv[1]