From d70f36333cc10707b0f1f6b8e8bf5d37ea5c5ea7 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Thu, 3 Jul 2025 15:14:45 -0300 Subject: [PATCH 01/12] feat: add new compliance wrapper --- Cargo.lock | 15 + contracts/compliance-wrapper/Cargo.toml | 35 ++ .../compliance-wrapper/src/bin/schema.rs | 11 + contracts/compliance-wrapper/src/contract.rs | 363 ++++++++++++++++++ contracts/compliance-wrapper/src/error.rs | 14 + contracts/compliance-wrapper/src/helpers.rs | 27 ++ contracts/compliance-wrapper/src/lib.rs | 7 + contracts/compliance-wrapper/src/msg.rs | 21 + contracts/compliance-wrapper/src/state.rs | 6 + 9 files changed, 499 insertions(+) create mode 100644 contracts/compliance-wrapper/Cargo.toml create mode 100644 contracts/compliance-wrapper/src/bin/schema.rs create mode 100644 contracts/compliance-wrapper/src/contract.rs create mode 100644 contracts/compliance-wrapper/src/error.rs create mode 100644 contracts/compliance-wrapper/src/helpers.rs create mode 100644 contracts/compliance-wrapper/src/lib.rs create mode 100644 contracts/compliance-wrapper/src/msg.rs create mode 100644 contracts/compliance-wrapper/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 7797b3b..179fbb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,21 @@ dependencies = [ "utils", ] +[[package]] +name = "compliance-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-wrapper/Cargo.toml b/contracts/compliance-wrapper/Cargo.toml new file mode 100644 index 0000000..59f5dc7 --- /dev/null +++ b/contracts/compliance-wrapper/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "compliance-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-wrapper/src/bin/schema.rs b/contracts/compliance-wrapper/src/bin/schema.rs new file mode 100644 index 0000000..cc39d04 --- /dev/null +++ b/contracts/compliance-wrapper/src/bin/schema.rs @@ -0,0 +1,11 @@ +use compliance_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-wrapper/src/contract.rs b/contracts/compliance-wrapper/src/contract.rs new file mode 100644 index 0000000..20e3b0f --- /dev/null +++ b/contracts/compliance-wrapper/src/contract.rs @@ -0,0 +1,363 @@ +#[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}; + + 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())) + } + + 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")) + } + + 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, + )?), + } +} + +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: if is_whitelisted(deps, from.clone()) { None } else { from }, + 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(&false).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-wrapper/src/error.rs b/contracts/compliance-wrapper/src/error.rs new file mode 100644 index 0000000..4b4168b --- /dev/null +++ b/contracts/compliance-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-wrapper/src/helpers.rs b/contracts/compliance-wrapper/src/helpers.rs new file mode 100644 index 0000000..6ffc885 --- /dev/null +++ b/contracts/compliance-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-wrapper/src/lib.rs b/contracts/compliance-wrapper/src/lib.rs new file mode 100644 index 0000000..233dbf5 --- /dev/null +++ b/contracts/compliance-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-wrapper/src/msg.rs b/contracts/compliance-wrapper/src/msg.rs new file mode 100644 index 0000000..79d3920 --- /dev/null +++ b/contracts/compliance-wrapper/src/msg.rs @@ -0,0 +1,21 @@ +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-wrapper/src/state.rs b/contracts/compliance-wrapper/src/state.rs new file mode 100644 index 0000000..64db773 --- /dev/null +++ b/contracts/compliance-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"); From 207e2cdb25fbe5533db51472822f338935beba4a Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Thu, 3 Jul 2025 15:15:49 -0300 Subject: [PATCH 02/12] feat: add incomplete new trex flow --- scripts/common.py | 43 +++++++++++++++++ scripts/compliance_setup.py | 16 ++++++- scripts/config.py | 2 + scripts/new_trex.py | 96 +++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100755 scripts/new_trex.py diff --git a/scripts/common.py b/scripts/common.py index 55a0b09..91d6f58 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -190,6 +190,49 @@ 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 + print(result) + pair_address = next( + attr["value"] + for event in result["tx_response"]["events"] + for attr in event["attributes"] + if attr["key"] == "pair_contract_addr" + ) + + # Return the contract address + return pair_address + + # 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..f9697b7 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 # @@ -48,6 +48,20 @@ def add_compliance_to_token(token_address): ) 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") + ######## # Call # ######## diff --git a/scripts/config.py b/scripts/config.py index ec36d5a..f16c736 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -18,6 +18,8 @@ "compliance_country_restriction_address": "kii1k4j6gr75k23tvqjdw9zvtrdxh7pxtexy5pdk7xjmwf385msx75fsz470kz", "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", } +CW20_BASE_CODE_ID = 160 +FACTORY_ADDRESS="kii1ct5yx003l9h2fsahug0vpx7xec4k66he0try0fclxg59fd4aztys4mnds9" # Management TRUSTED_ISSUER_KEY_NAME = "trusted_issuer" diff --git a/scripts/new_trex.py b/scripts/new_trex.py new file mode 100755 index 0000000..f935487 --- /dev/null +++ b/scripts/new_trex.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +import config +import sys +from common import create_pair, instantiate_contract, store_contract +from compliance_setup import add_compliance_claim_to_token, add_compliance_to_token + +############################# +# 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 # +############# + +# Example json setup +# cw20_base_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"], +# }, +# } + +# Deploy trex creates a new instance of a trex contract with the default owner +def deploy_trex(json_setup): + print("Deploying trex ") + trex_base_address = instantiate_contract( + CW20_BASE_CODE_ID, json_setup, "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): + 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"Giving an identity to pair {pair_address}") + print("Adding liquidity to it") + +# setup trex deploys and sets up a pool and compliance for a trex +def setup_trex(json_setup): + print("Setting up new trex token") + # Deploy new instance of contract with our default owner + trex_address = deploy_trex(json_setup) + + # 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) + + # Adding claim to token, assuming default topic 1 + add_compliance_claim_to_token(trex_address) + +# Flow to add a new asset +# - Create a new CW_20 +# - Add compliances modules to token +# - Add claim topic to token +# - Create a new pair between CW_20 and native token +# - This will be a pool +# - In the future we might want different pairs +# - Set up CW_20 compliance +# - We need to register a claim limitation for the token +# - We specify a topic (we using 1 for now) +# - Needs a trusted issuer From 6b8dde66a08243476c083d18bebcb12163396acd Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 11:11:51 -0300 Subject: [PATCH 03/12] feat: deploy compliance wrapper --- scripts/config.py | 2 ++ scripts/deploy.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/scripts/config.py b/scripts/config.py index f16c736..edea7b5 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -16,6 +16,8 @@ "compliance_registry_address": "kii1dpws8lmu2l4awdau6zhezxm97tshu427aulr2xwp7uarpd8jqu7srjz2gm", "compliance_claims_address": "kii12n3frtnx8mh2vpvzk7mqkr6yqkfe77339c7uakzqqa4pv46zdr9qt020h3", "compliance_country_restriction_address": "kii1k4j6gr75k23tvqjdw9zvtrdxh7pxtexy5pdk7xjmwf385msx75fsz470kz", + "compliance_country_wrapper_address" : "kii125u5ufx0jfelud68zn40dyvk8gfsat24s7v322jwnxghjtve7rtqsqchja", + "compliance_claim_wrapper_address" : "kii1xlz8y3v0u0z428tvhdlqh930rmnafy84ecjc37gxacd02remdl8s00sh94", "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", } CW20_BASE_CODE_ID = 160 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) From 7989b4e7e2f6e8d753970f3cc2303f8521f76982 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 11:18:27 -0300 Subject: [PATCH 04/12] fix: add script to remove old compliance and update with compliance wrapper --- scripts/compliance_setup.py | 38 +++++++++++++++++++++++++++++++++---- scripts/config.py | 2 +- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/scripts/compliance_setup.py b/scripts/compliance_setup.py index f9697b7..ef37e5b 100755 --- a/scripts/compliance_setup.py +++ b/scripts/compliance_setup.py @@ -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,8 +70,8 @@ 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, diff --git a/scripts/config.py b/scripts/config.py index edea7b5..fb357e6 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -16,8 +16,8 @@ "compliance_registry_address": "kii1dpws8lmu2l4awdau6zhezxm97tshu427aulr2xwp7uarpd8jqu7srjz2gm", "compliance_claims_address": "kii12n3frtnx8mh2vpvzk7mqkr6yqkfe77339c7uakzqqa4pv46zdr9qt020h3", "compliance_country_restriction_address": "kii1k4j6gr75k23tvqjdw9zvtrdxh7pxtexy5pdk7xjmwf385msx75fsz470kz", + "compliance_claims_wrapper_address" : "kii1xlz8y3v0u0z428tvhdlqh930rmnafy84ecjc37gxacd02remdl8s00sh94", "compliance_country_wrapper_address" : "kii125u5ufx0jfelud68zn40dyvk8gfsat24s7v322jwnxghjtve7rtqsqchja", - "compliance_claim_wrapper_address" : "kii1xlz8y3v0u0z428tvhdlqh930rmnafy84ecjc37gxacd02remdl8s00sh94", "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", } CW20_BASE_CODE_ID = 160 From 0451f8e21d43820acdec9e750d10c5e6ebf83117 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 15:20:41 -0300 Subject: [PATCH 05/12] feat: add new t-rex flow --- scripts/common.py | 85 ++++++++++++++++++++++++++++++++++++- scripts/compliance_setup.py | 23 ++++++++++ scripts/new_trex.py | 55 ++++++++++++++++-------- scripts/user_setup.py | 1 + 4 files changed, 145 insertions(+), 19 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index 91d6f58..c196515 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -226,12 +226,95 @@ def create_pair(factory_address, msg, from_key): attr["value"] for event in result["tx_response"]["events"] for attr in event["attributes"] - if attr["key"] == "pair_contract_addr" + 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): diff --git a/scripts/compliance_setup.py b/scripts/compliance_setup.py index ef37e5b..da03acd 100755 --- a/scripts/compliance_setup.py +++ b/scripts/compliance_setup.py @@ -92,6 +92,29 @@ def add_compliance_claim_to_token(token_address): ) 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/new_trex.py b/scripts/new_trex.py index f935487..84523cd 100755 --- a/scripts/new_trex.py +++ b/scripts/new_trex.py @@ -2,8 +2,8 @@ import config import sys -from common import create_pair, instantiate_contract, store_contract -from compliance_setup import add_compliance_claim_to_token, add_compliance_to_token +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 # @@ -21,19 +21,6 @@ # Functions # ############# -# Example json setup -# cw20_base_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"], -# }, -# } - # Deploy trex creates a new instance of a trex contract with the default owner def deploy_trex(json_setup): print("Deploying trex ") @@ -44,7 +31,7 @@ def deploy_trex(json_setup): return trex_base_address # create liquidity creates a token pair associated with the TREX and gives it liquidity -def create_liquidity(token_address): +def create_liquidity(token_address, cw20_amount, native_amount): print("Creating pair to kii ") pair_msg = { "create_pair": { @@ -67,8 +54,14 @@ def create_liquidity(token_address): } } pair_address = create_pair(FACTORY_ADDRESS, pair_msg, OWNER_KEY_NAME) - print(f"Giving an identity to pair {pair_address}") - print("Adding liquidity to it") + 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(json_setup): @@ -76,10 +69,16 @@ def setup_trex(json_setup): # Deploy new instance of contract with our default owner trex_address = deploy_trex(json_setup) + # Create pair and liquidity + pair_address = create_liquidity(trex_address) + # 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) @@ -94,3 +93,23 @@ def setup_trex(json_setup): # - We need to register a claim limitation for the token # - We specify a topic (we using 1 for now) # - Needs a trusted issuer + +# Example json setup +# cw20_base_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"], +# }, +# } + +######## +# Call # +######## + +if __name__== "__main__": + create_liquidity(config.CONTRACTS["cw20_base_address"], 1000, 10000000000) 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] From 65daa0bcfa01a0d0bbd5c663b32e988d55675256 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 15:31:58 -0300 Subject: [PATCH 06/12] fix: get deploy working --- scripts/common.py | 1 - scripts/config.py | 2 +- scripts/new_trex.py | 53 ++++++++++++++++++--------------------------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/scripts/common.py b/scripts/common.py index c196515..61afa47 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -221,7 +221,6 @@ def create_pair(factory_address, msg, from_key): result = check_tx_until_result(tx_hash) # Get the pair address from the result - print(result) pair_address = next( attr["value"] for event in result["tx_response"]["events"] diff --git a/scripts/config.py b/scripts/config.py index fb357e6..267a250 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -20,7 +20,7 @@ "compliance_country_wrapper_address" : "kii125u5ufx0jfelud68zn40dyvk8gfsat24s7v322jwnxghjtve7rtqsqchja", "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", } -CW20_BASE_CODE_ID = 160 +CW20_BASE_CODE_ID = "160" FACTORY_ADDRESS="kii1ct5yx003l9h2fsahug0vpx7xec4k66he0try0fclxg59fd4aztys4mnds9" # Management diff --git a/scripts/new_trex.py b/scripts/new_trex.py index 84523cd..e8e6b14 100755 --- a/scripts/new_trex.py +++ b/scripts/new_trex.py @@ -22,10 +22,21 @@ ############# # Deploy trex creates a new instance of a trex contract with the default owner -def deploy_trex(json_setup): - print("Deploying trex ") +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, json_setup, "T-REX", OWNER_KEY_NAME + 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 @@ -64,13 +75,16 @@ def create_liquidity(token_address, cw20_amount, native_amount): return pair_address # setup trex deploys and sets up a pool and compliance for a trex -def setup_trex(json_setup): +def setup_trex(): print("Setting up new trex token") # Deploy new instance of contract with our default owner - trex_address = deploy_trex(json_setup) + trex_address = deploy_trex() # Create pair and liquidity - pair_address = create_liquidity(trex_address) + # 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 @@ -82,34 +96,9 @@ def setup_trex(json_setup): # Adding claim to token, assuming default topic 1 add_compliance_claim_to_token(trex_address) -# Flow to add a new asset -# - Create a new CW_20 -# - Add compliances modules to token -# - Add claim topic to token -# - Create a new pair between CW_20 and native token -# - This will be a pool -# - In the future we might want different pairs -# - Set up CW_20 compliance -# - We need to register a claim limitation for the token -# - We specify a topic (we using 1 for now) -# - Needs a trusted issuer - -# Example json setup -# cw20_base_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"], -# }, -# } - ######## # Call # ######## if __name__== "__main__": - create_liquidity(config.CONTRACTS["cw20_base_address"], 1000, 10000000000) + setup_trex() From 4e0ff0ea7571ed3b996dc71a768224a5456e4079 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 15:45:35 -0300 Subject: [PATCH 07/12] chore: run cargo fmt --- contracts/compliance-wrapper/src/contract.rs | 40 ++++++++++++++------ contracts/compliance-wrapper/src/msg.rs | 12 ++---- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/contracts/compliance-wrapper/src/contract.rs b/contracts/compliance-wrapper/src/contract.rs index 20e3b0f..edbb5d5 100644 --- a/contracts/compliance-wrapper/src/contract.rs +++ b/contracts/compliance-wrapper/src/contract.rs @@ -1,6 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; use cw2::set_contract_version; use crate::error::ContractError; @@ -63,9 +65,9 @@ pub fn execute( execute::check_role(deps.as_ref(), info.sender, OwnerRole::ComplianceManager)?; match msg { - ExecuteMsg::ChangeComplianceModule { - module_address, - } => execute::change_compliance_module(deps, module_address), + ExecuteMsg::ChangeComplianceModule { module_address } => { + execute::change_compliance_module(deps, module_address) + } ExecuteMsg::AddAddressToWhitelist { address } => { execute::add_address_to_whitelist(deps, address) } @@ -114,7 +116,7 @@ pub mod execute { deps: DepsMut, address: Addr, ) -> Result { - WHITELISTED_ADDRESSES.save(deps.storage, address.clone(), &true,)?; + WHITELISTED_ADDRESSES.save(deps.storage, address.clone(), &true)?; Ok(Response::new().add_attribute("action", "add_address_to_whitelist")) } @@ -148,7 +150,7 @@ pub fn query(deps: Deps, _env: Env, msg: utils::compliance::QueryMsg) -> StdResu fn is_whitelisted(deps: Deps, address: Option) -> bool { match address { None => true, - Some(addr) => WHITELISTED_ADDRESSES.load(deps.storage, addr).is_ok() + Some(addr) => WHITELISTED_ADDRESSES.load(deps.storage, addr).is_ok(), } } @@ -168,13 +170,25 @@ pub mod query { 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 }; + 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: if is_whitelisted(deps, from.clone()) { None } else { from }, + from: if is_whitelisted(deps, from.clone()) { + None + } else { + from + }, to: to.clone(), amount, }; @@ -221,7 +235,9 @@ mod tests { 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(); + let compliance_module = COMPLIANCE_MODULE_ADDRESS + .load(deps.as_ref().storage) + .unwrap(); assert_eq!(compliance_module, Addr::unchecked("compliance_module")); } @@ -253,7 +269,7 @@ mod tests { _ => panic!("Unexpected query type"), }); let info = message_info(&Addr::unchecked("admin"), &[]); - let msg = ExecuteMsg::ChangeComplianceModule{ + let msg = ExecuteMsg::ChangeComplianceModule { module_address: Addr::unchecked("module"), }; @@ -298,7 +314,7 @@ mod tests { // Now remove the module let remove_msg = ExecuteMsg::RemoveAddressFromWhitelist { - address: Addr::unchecked("address"), + address: Addr::unchecked("address"), }; let res = execute(deps.as_mut(), mock_env(), info, remove_msg).unwrap(); assert_eq!(1, res.attributes.len()); diff --git a/contracts/compliance-wrapper/src/msg.rs b/contracts/compliance-wrapper/src/msg.rs index 79d3920..fd4410c 100644 --- a/contracts/compliance-wrapper/src/msg.rs +++ b/contracts/compliance-wrapper/src/msg.rs @@ -9,13 +9,7 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - ChangeComplianceModule { - module_address: Addr, - }, - AddAddressToWhitelist { - address: Addr, - }, - RemoveAddressFromWhitelist { - address: Addr, - }, + ChangeComplianceModule { module_address: Addr }, + AddAddressToWhitelist { address: Addr }, + RemoveAddressFromWhitelist { address: Addr }, } From cd1fb3e9d2d5411297aa50916f0ad9d279abca81 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 15:47:27 -0300 Subject: [PATCH 08/12] fix: rollback change to test --- contracts/compliance-wrapper/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/compliance-wrapper/src/contract.rs b/contracts/compliance-wrapper/src/contract.rs index edbb5d5..a10daa5 100644 --- a/contracts/compliance-wrapper/src/contract.rs +++ b/contracts/compliance-wrapper/src/contract.rs @@ -358,7 +358,7 @@ mod tests { from: _, to: _, amount: _, - } => SystemResult::Ok(ContractResult::Ok(to_json_binary(&false).unwrap())), + } => SystemResult::Ok(ContractResult::Ok(to_json_binary(&true).unwrap())), } } _ => panic!("Unexpected query type"), From c4fe13db71d387552abf2ee41d76eab2af0c2fcd Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 16:01:44 -0300 Subject: [PATCH 09/12] fix: remove duplicated is_whitelisted check for from --- contracts/compliance-wrapper/src/contract.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/compliance-wrapper/src/contract.rs b/contracts/compliance-wrapper/src/contract.rs index a10daa5..6b792ab 100644 --- a/contracts/compliance-wrapper/src/contract.rs +++ b/contracts/compliance-wrapper/src/contract.rs @@ -184,11 +184,7 @@ pub mod query { // Check compliance with wrapped module let msg = utils::compliance::QueryMsg::CheckTokenCompliance { token_address: token_address.clone(), - from: if is_whitelisted(deps, from.clone()) { - None - } else { - from - }, + from: from.clone(), to: to.clone(), amount, }; From ac50b2c19adf3c63daa6482944633e761c00f7bd Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 16:04:08 -0300 Subject: [PATCH 10/12] docs: add missing docstrings --- contracts/compliance-wrapper/src/contract.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/compliance-wrapper/src/contract.rs b/contracts/compliance-wrapper/src/contract.rs index 6b792ab..4fcba23 100644 --- a/contracts/compliance-wrapper/src/contract.rs +++ b/contracts/compliance-wrapper/src/contract.rs @@ -84,6 +84,7 @@ pub mod execute { 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 }; @@ -112,6 +113,7 @@ pub mod execute { .add_attribute("module_address", module_address.to_string())) } + /// Adds an address to the whitelist pub fn add_address_to_whitelist( deps: DepsMut, address: Addr, @@ -120,6 +122,7 @@ pub mod execute { 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, @@ -147,6 +150,7 @@ pub fn query(deps: Deps, _env: Env, msg: utils::compliance::QueryMsg) -> StdResu } } +/// Check if a given address is whitelisted fn is_whitelisted(deps: Deps, address: Option) -> bool { match address { None => true, From 3659c19a2ec9517764b52e8c524927a87bb77ef4 Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 16:07:54 -0300 Subject: [PATCH 11/12] chore: rename contract --- Cargo.lock | 2 +- .../Cargo.toml | 2 +- .../src/bin/schema.rs | 2 +- .../src/contract.rs | 0 .../src/error.rs | 0 .../src/helpers.rs | 0 .../src/lib.rs | 0 .../src/msg.rs | 0 .../src/state.rs | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/Cargo.toml (96%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/bin/schema.rs (73%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/contract.rs (100%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/error.rs (100%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/helpers.rs (100%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/lib.rs (100%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/msg.rs (100%) rename contracts/{compliance-wrapper => compliance-whitelist-wrapper}/src/state.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 179fbb9..362fcc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,7 +298,7 @@ dependencies = [ ] [[package]] -name = "compliance-wrapper" +name = "compliance-whitelist-wrapper" version = "0.1.0" dependencies = [ "cosmwasm-schema", diff --git a/contracts/compliance-wrapper/Cargo.toml b/contracts/compliance-whitelist-wrapper/Cargo.toml similarity index 96% rename from contracts/compliance-wrapper/Cargo.toml rename to contracts/compliance-whitelist-wrapper/Cargo.toml index 59f5dc7..7dddfd5 100644 --- a/contracts/compliance-wrapper/Cargo.toml +++ b/contracts/compliance-whitelist-wrapper/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "compliance-wrapper" +name = "compliance-whitelist-wrapper" version = "0.1.0" authors = ["Thales Zirbel "] edition = "2021" diff --git a/contracts/compliance-wrapper/src/bin/schema.rs b/contracts/compliance-whitelist-wrapper/src/bin/schema.rs similarity index 73% rename from contracts/compliance-wrapper/src/bin/schema.rs rename to contracts/compliance-whitelist-wrapper/src/bin/schema.rs index cc39d04..7e24478 100644 --- a/contracts/compliance-wrapper/src/bin/schema.rs +++ b/contracts/compliance-whitelist-wrapper/src/bin/schema.rs @@ -1,4 +1,4 @@ -use compliance_wrapper::msg::{ExecuteMsg, InstantiateMsg}; +use compliance_whitelist_wrapper::msg::{ExecuteMsg, InstantiateMsg}; use cosmwasm_schema::write_api; use utils::compliance::QueryMsg; diff --git a/contracts/compliance-wrapper/src/contract.rs b/contracts/compliance-whitelist-wrapper/src/contract.rs similarity index 100% rename from contracts/compliance-wrapper/src/contract.rs rename to contracts/compliance-whitelist-wrapper/src/contract.rs diff --git a/contracts/compliance-wrapper/src/error.rs b/contracts/compliance-whitelist-wrapper/src/error.rs similarity index 100% rename from contracts/compliance-wrapper/src/error.rs rename to contracts/compliance-whitelist-wrapper/src/error.rs diff --git a/contracts/compliance-wrapper/src/helpers.rs b/contracts/compliance-whitelist-wrapper/src/helpers.rs similarity index 100% rename from contracts/compliance-wrapper/src/helpers.rs rename to contracts/compliance-whitelist-wrapper/src/helpers.rs diff --git a/contracts/compliance-wrapper/src/lib.rs b/contracts/compliance-whitelist-wrapper/src/lib.rs similarity index 100% rename from contracts/compliance-wrapper/src/lib.rs rename to contracts/compliance-whitelist-wrapper/src/lib.rs diff --git a/contracts/compliance-wrapper/src/msg.rs b/contracts/compliance-whitelist-wrapper/src/msg.rs similarity index 100% rename from contracts/compliance-wrapper/src/msg.rs rename to contracts/compliance-whitelist-wrapper/src/msg.rs diff --git a/contracts/compliance-wrapper/src/state.rs b/contracts/compliance-whitelist-wrapper/src/state.rs similarity index 100% rename from contracts/compliance-wrapper/src/state.rs rename to contracts/compliance-whitelist-wrapper/src/state.rs From 64c6eb0f5e0c5b3297809f29e775a66d40027edc Mon Sep 17 00:00:00 2001 From: Thales Zirbel Date: Fri, 4 Jul 2025 16:10:17 -0300 Subject: [PATCH 12/12] docs: add a simple readme --- contracts/compliance-whitelist-wrapper/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 contracts/compliance-whitelist-wrapper/README.md 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.