diff --git a/.gitignore b/.gitignore index 9095dea..539aaeb 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,9 @@ # IDEs *.iml .idea + +# Artifacts from builds +artifacts/ + +# Python artifacts +__pycache__/ diff --git a/README.md b/README.md index a26a924..1c2a099 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,11 @@ Extends the CW20 standard with T-REX functionalities and implements permissioned #### Storage - `token_info`: TokenInfo + - Name, symbol, decimals, supply and minter(opt) + - Minter has an addr and can have a cap - `balances`: Map - `allowances`: Map<(Addr, Addr), AllowanceInfo> + - uint with an expiration date - `identity_registry`: Addr - `compliance`: Addr @@ -28,6 +31,13 @@ Extends the CW20 standard with T-REX functionalities and implements permissioned - Description: Transfers tokens on behalf of the owner if the recipient is verified and compliant. - Interaction: Similar to `transfer`, but checks allowances first. +- `send(contract: Addr, amount: Uint128, msg: Binary) -> Result` + - Msg is weird `Binary::from(r#"{"some":123}"#.as_bytes());` +- `burn(amount: Uint128) -> Result` +- `mint(contract: Addr, amount: Uint128) -> Result` +- Increase/Decrease Allowance + - Can also change expiration + #### Query - `balance(address: Addr) -> BalanceResponse` - `token_info() -> TokenInfoResponse` @@ -77,7 +87,7 @@ Stores the mapping of wallet addresses to identity contracts. ### 4. Trusted Issuers Registry -Manages the list of trusted claim issuers. +Manages the list of trusted claim issuers. OwnerRole::IssuersRegistryManager can change trusted issuers. #### Storage - `trusted_issuers`: Map @@ -92,21 +102,23 @@ Manages the list of trusted claim issuers. ### 5. Claim Topics Registry -Stores the required claim topics for token eligibility. +Stores the required claim topics for token eligibility. It is used for compliance. Only those with role OwnerRole::ClaimRegistryManager can change this. #### Storage -- `required_claim_topics`: Vec +- `token_claim_topics`: Vec + - Token addresses are mapped to claims + - Claim is defined by a topic uint128 and an active bool #### Execute - `add_claim_topic(topic: u32) -> Result` - `remove_claim_topic(topic: u32) -> Result` #### Query -- `get_required_claim_topics() -> GetRequiredClaimTopicsResponse` +- `get_claims_for_token() -> StdResult>` ### 6. Modular Compliance -Implements transfer restriction rules. +Implements transfer restriction rules. Only OwnerRole::ComplianceManager can execute changes. For now country compliance and claims compliance are the ones in use. #### Storage - `modules`: Vec diff --git a/contracts-review.md b/contracts-review.md new file mode 100644 index 0000000..94fe80d --- /dev/null +++ b/contracts-review.md @@ -0,0 +1,221 @@ +# General +## Deploy order +- OwnerRoles +- AgentRoles (weirdly not used by others) +- TrustedIssuers (needs owner roles) +- ClaimTopics (needs owner roles) +- OnChainId (needs trusted issuer) +- ComplianceRegistry (needs owner roles) +- ComplianceClaims (needs chainId, owner roles and claim topics) +- ComplianceCountry (needs chainId and owner roles) +- CWBase (needs compliance registry) + +# Owner roles +- Stores owner, there is only a single one +- Adds verification if a specific address her a specific owner role +- Lets owner add other addresses with specific owner roles +- Stores compliance, issuer and claim registries + - IssuersRegistryManagers can add/remove trusted issuers + - ClaimRegistryManagers can add/remove topics + - Looks like unfinished center hub + +## Owner roles +```rust +pub enum OwnerRole { + OwnerAdmin, + RegistryAddressSetter, + ComplianceSetter, + ComplianceManager, + ClaimRegistryManager, + IssuersRegistryManager, + TokenInfoManager, +} +``` + +# Agent roles +- Very similar to owner roles but with agent roles + - Supply Modifiers can do mint/burn + - Transfer manager can do transfer from +- Looks like unfinished center hub +- Has placeholder implementations + - Is transfer allowed + - can transfer + - can receive +```rust +pub enum AgentRole { + SupplyModifiers, + Freezers, + TransferManager, + RecoveryAgents, + ComplianceAgent, + WhiteListManages, + AgentAdmin, +} +``` + +# Trusted issuer +- Checks if an address is a trusted issuer + - They are tied with a vector of claim topics +- OwnerRole::IssuersRegistryManager can change list + +# on chain Identity +- Holds address, country, keys and claims for an identity +- ManagementKey can change stuff +- Needs to check if an user is trusted to allow changes to execute remove/add claims + - Centralizes that +- I dont understand fully how to use this contract + - Do a centralized trusted add other identities? They add themselves? + +## Key types +- I dont quite get the key types +```rust +pub enum KeyType { + // 1: MANAGEMENT keys, which can manage the identity + ManagementKey, + // 2: EXECUTION keys, which perform actions in this identities name (signing, logins, transactions, etc.) + ExecutionKey, + // 3: CLAIM signer keys, used to sign claims on other identities which need to be revokable. + ClaimSignerKey, + // 4: ENCRYPTION keys, used to encrypt data e.g. hold in claims. + EncryptionKey, +} +``` + +# Compliance contracts +- Limit usage of a given contract + - I.e restrict US for cw20 + +## Compliance Registry +- Handles compliance modules + - When checking if compliant, checks every submodule for valid compliance +- Reqs owner roles + - Only compliance manager can change stuff + +## Compliance country restriction +- Restricts which countries comply +- Only compliance manager can add or remove restrictions +- Queries identities' country to check if valid + - Utilizes a contract that holds identity addresses + - OnChainId handles identities + +## Compliance claims +- Restricts usage based on claim topics +- Only query to check claims and compliance + - Compliance checks the claim topics + +### Claim Topics +- Holds token address <> topic association +- Claims are a topic + active bool +- ClaimRegistryManager can change topics + - `I didn't quite get this` + +# CW20 base +## Optionals +Full info: https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md +- Mintable + - Allows query of Minter +- Allowance + - Allows query of allowance +- Enumerable + - Allows queries of 'all' +- Marketing + - Allows more metadata info (description, logo) + - Download logo + +## Compliance +- A token address is stored +- It is used to send a message to that address, to check compliance + - Via smart wasm query +- On every burn, transfer, mint and sent + - It checks if the from, to and amount complies + +# Yet to Review +- Missing Executes of CW20 + - Just checked instantiate +- Usage test + + +# Flows +A tool centralizes txs of the tokens + +We need to understand how to do these flows +## Deploy +Owner can issue a pool +- Pool is a trusted issuers + - Can receive sells and send out buys +## Register KYC +## Buy +- Users enters a website +- They got through a KYC +- To buy they will go through a DEX + - A liquidity pool that is trusted + - Both the pool and the buyer will need to have a topic level that matches one for the tokens +## Sell +- Goes from pool to buyer + +## Questions +- how do we buy/sell assets into this? + + +# Enzo's experiences +## Economic rights +- Can hold the token but other person can claim things for you + +## Swap place +- Listing and minting +- Swap: Takes best price and match +- Listing: lists sell/buys + +## Proof of reserve +- Users can ask for proof of reserves +- Contracts have restrictions + - Tokens needs to have a link to the rights +- We, as holders, need to have custody and proof of it + - Else we can be sued + +## Living on the chain +- If you move to another chain, it will lose it's value + - No longer tied in the contracts +- If they hold a copy, then it will just be mirrored + - Would need to be copied + +## Money Laundry +- Compliance should have a way to deal with possible MLs + +## What does plume do? +- https://plume.org/ + + +# Current Direction +1. Deploy RWA contracts +2. Deploy astroport environment (DEX contracts) +3. Deploy preparations for RWA +4. Create trusted issuer + - Backend connects issuers to claims +5. Create a pool between the RWA token and MockUSDT + - Need to check if Mock usdt (it is an ERC20 and needs to be native to the chain) + - Pool needs to be a trusted issuer +6. Users should be able to claim + - Go to RWA page + - Can see tokens but not buy + - Sign up KYC to become trusted + - Can do swaps + +Notes +- We will need to interact via cosmwasm precompile +- [Astroport](https://astroport.fi/) has a lot of flexibility on tokens used +- This is a direction, we need to make a proper plan in the future + +## Problems +- Only Cosmos addresses are valid via compliance? + - How can we add EVM users to that? Will EVM work out the bat + - Wrap the cosm? + - If this gets out of these contracts, it loses compliance +- Swap can only be done between two trusted issuers + - Do we want swap? +- New assets are done by hand +- CW20 contract is not enforcing roles correctly +- No mention to yield, distribution and so on +- Are allowances ok? +- Compliance registry has no method to list compliances + - Add always overwrites if applying to same token/name diff --git a/contracts/claim-topics/src/contract.rs b/contracts/claim-topics/src/contract.rs index 665f594..93768d9 100644 --- a/contracts/claim-topics/src/contract.rs +++ b/contracts/claim-topics/src/contract.rs @@ -27,7 +27,7 @@ pub fn instantiate( .api .addr_validate(msg.owner_roles_address.as_ref()) .map_err(|e| ContractError::InvalidAddress { - reason: format!("Invalid owner address: {}", e), + reason: format!("Invalid owner address: {e}"), })?; OWNER_ROLES_ADDRESS.save(deps.storage, &owner_addr)?; diff --git a/contracts/cw20-base/src/allowances.rs b/contracts/cw20-base/src/allowances.rs index f5d0519..f653f46 100644 --- a/contracts/cw20-base/src/allowances.rs +++ b/contracts/cw20-base/src/allowances.rs @@ -261,7 +261,7 @@ mod tests { use cw20::{Cw20Coin, TokenInfoResponse}; use crate::contract::{execute, instantiate, query_balance, query_token_info}; - use crate::msg::{ExecuteMsg, InstantiateMsg, InstantiateTokenInfo, Registeries}; + use crate::msg::{ExecuteMsg, InstantiateMsg, InstantiateTokenInfo, Registries}; fn get_balance>(deps: Deps, address: T) -> Uint128 { query_balance(deps, address.into()).unwrap().balance @@ -285,7 +285,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; diff --git a/contracts/cw20-base/src/contract.rs b/contracts/cw20-base/src/contract.rs index 98320ff..060350b 100644 --- a/contracts/cw20-base/src/contract.rs +++ b/contracts/cw20-base/src/contract.rs @@ -126,9 +126,7 @@ pub fn instantiate( total_supply, mint, }; - let compliance_addr = deps - .api - .addr_validate(&msg.registeries.compliance_address)?; + let compliance_addr = deps.api.addr_validate(&msg.registries.compliance_address)?; COMPLIANCE_ADDRESS.save(deps.storage, &compliance_addr)?; TOKEN_INFO.save(deps.storage, &data)?; @@ -684,7 +682,7 @@ mod tests { }; use super::*; - use crate::msg::{InstantiateMarketingInfo, InstantiateTokenInfo, Registeries}; + use crate::msg::{InstantiateMarketingInfo, InstantiateTokenInfo, Registries}; use utils::compliance::QueryMsg::CheckTokenCompliance; fn get_balance>(deps: Deps, address: T) -> Uint128 { @@ -734,7 +732,7 @@ mod tests { mint: mint.clone(), marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -780,7 +778,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -823,7 +821,7 @@ mod tests { }), marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -873,7 +871,7 @@ mod tests { }), marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -909,7 +907,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default() .addr_make("compliance_addr") .to_string(), @@ -954,7 +952,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default() .addr_make("compliance_addr") .to_string(), @@ -1234,7 +1232,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1261,7 +1259,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1617,7 +1615,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default() .addr_make("compliance_addr") .to_string(), @@ -1729,7 +1727,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1790,7 +1788,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1850,7 +1848,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1910,7 +1908,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -1970,7 +1968,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2031,7 +2029,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2090,7 +2088,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2152,7 +2150,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2212,7 +2210,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2268,7 +2266,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2326,7 +2324,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2385,7 +2383,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2442,7 +2440,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2506,7 +2504,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -2563,7 +2561,7 @@ mod tests { logo: Some(Logo::Url("url".to_owned())), }), }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; diff --git a/contracts/cw20-base/src/enumerable.rs b/contracts/cw20-base/src/enumerable.rs index 5bd6232..94effa9 100644 --- a/contracts/cw20-base/src/enumerable.rs +++ b/contracts/cw20-base/src/enumerable.rs @@ -89,7 +89,7 @@ mod tests { use cw20::{Cw20Coin, Expiration, TokenInfoResponse}; use crate::contract::{execute, instantiate, query, query_token_info}; - use crate::msg::{ExecuteMsg, InstantiateMsg, InstantiateTokenInfo, QueryMsg, Registeries}; + use crate::msg::{ExecuteMsg, InstantiateMsg, InstantiateTokenInfo, QueryMsg, Registries}; // this will set up the instantiation for other tests fn do_instantiate(mut deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse { @@ -105,7 +105,7 @@ mod tests { mint: None, marketing: None, }, - registeries: Registeries { + registries: Registries { compliance_address: MockApi::default().addr_make("compliance_addr").to_string(), }, }; @@ -296,7 +296,7 @@ mod tests { do_instantiate(deps.as_mut(), &acct1, Uint128::new(12340000)); - // put money everywhere (to create balanaces) + // put money everywhere (to create balances) let info = message_info(&Addr::unchecked(acct1.clone()), &[]); let env = mock_env(); execute( diff --git a/contracts/cw20-base/src/msg.rs b/contracts/cw20-base/src/msg.rs index 1baba6b..bcf7019 100644 --- a/contracts/cw20-base/src/msg.rs +++ b/contracts/cw20-base/src/msg.rs @@ -29,12 +29,12 @@ pub struct InstantiateTokenInfo { #[cfg_attr(test, derive(Default))] pub struct InstantiateMsg { pub token_info: InstantiateTokenInfo, - pub registeries: Registeries, + pub registries: Registries, } #[cw_serde] #[derive(Default)] -pub struct Registeries { +pub struct Registries { pub compliance_address: String, } @@ -143,7 +143,7 @@ mod tests { use super::*; #[test] - fn validate_instantiatemsg_name() { + fn validate_instantiate_msg_name() { // Too short let mut msg = InstantiateMsg { token_info: InstantiateTokenInfo { @@ -164,7 +164,7 @@ mod tests { } #[test] - fn validate_instantiatemsg_symbol() { + fn validate_instantiate_msg_symbol() { // Too short let mut msg = InstantiateMsg { diff --git a/contracts/on_chain_id/src/claim_management.rs b/contracts/on_chain_id/src/claim_management.rs index a765745..ccc2ac6 100644 --- a/contracts/on_chain_id/src/claim_management.rs +++ b/contracts/on_chain_id/src/claim_management.rs @@ -27,7 +27,7 @@ pub fn execute_add_claim( if !is_trusted { return Err(ContractError::Unauthorized { - reason: "Sender does not have persmission to add claim".to_string(), + reason: "Sender does not have permission to add claim".to_string(), }); } diff --git a/contracts/on_chain_id/src/contract.rs b/contracts/on_chain_id/src/contract.rs index 2faf3c6..d7b645f 100644 --- a/contracts/on_chain_id/src/contract.rs +++ b/contracts/on_chain_id/src/contract.rs @@ -29,8 +29,7 @@ pub fn instantiate( ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION).map_err(|e| { ContractError::Std(StdError::generic_err(format!( - "Failed to set contract version: {}", - e + "Failed to set contract version: {e}" ))) })?; @@ -38,7 +37,7 @@ pub fn instantiate( .api .addr_validate(&msg.owner) .map_err(|e| ContractError::InvalidAddress { - reason: format!("Invalid owner address: {}", e), + reason: format!("Invalid owner address: {e}"), })?; // Save the owner @@ -53,7 +52,7 @@ pub fn instantiate( .api .addr_validate(&msg.trusted_issuer_addr) .map_err(|e| ContractError::InvalidAddress { - reason: format!("Invalid trusted issuer address: {}", e), + reason: format!("Invalid trusted issuer address: {e}"), })?; // Save the trusted issuers address @@ -186,8 +185,7 @@ fn query_key( .cloned() .ok_or_else(|| { StdError::not_found(format!( - "Key not found for owner {} and type {:?} in identity {}", - key_owner, key_type, identity_owner + "Key not found for owner {key_owner} and type {key_type:?} in identity {identity_owner}" )) }) } diff --git a/contracts/on_chain_id/src/key_management.rs b/contracts/on_chain_id/src/key_management.rs index 38b41c4..5771043 100644 --- a/contracts/on_chain_id/src/key_management.rs +++ b/contracts/on_chain_id/src/key_management.rs @@ -27,7 +27,7 @@ pub fn execute_add_key( &identity_owner_addr, ) .map_err(|e| ContractError::Unauthorized { - reason: format!("Sender lacks ManagementKey: {}", e), + reason: format!("Sender lacks ManagementKey: {e}"), })?; let addr_key_owner = deps.api.addr_validate(&key_owner)?; @@ -89,7 +89,7 @@ pub fn execute_remove_key( &identity_owner_addr, ) .map_err(|e| ContractError::Unauthorized { - reason: format!("Sender lacks ManagementKey: {}", e), + reason: format!("Sender lacks ManagementKey: {e}"), })?; let addr_key_owner = deps.api.addr_validate(&key_owner)?; diff --git a/contracts/on_chain_id/src/utils.rs b/contracts/on_chain_id/src/utils.rs index 066270f..cd40596 100644 --- a/contracts/on_chain_id/src/utils.rs +++ b/contracts/on_chain_id/src/utils.rs @@ -31,7 +31,7 @@ pub fn check_key_authorization( Ok(()) } else { Err(ContractError::Unauthorized { - reason: format!("Sender lacks required key type: {:?}", required_key), + reason: format!("Sender lacks required key type: {required_key:?}"), }) } } diff --git a/contracts/owner-roles/src/contract.rs b/contracts/owner-roles/src/contract.rs index 520f581..525bda2 100644 --- a/contracts/owner-roles/src/contract.rs +++ b/contracts/owner-roles/src/contract.rs @@ -243,7 +243,7 @@ pub mod execute { .add_message(msg) .add_attribute("action", "add_trusted_issuer") .add_attribute("issuer", issuer.to_string()) - .add_attribute("claim_topics", format!("{:?}", claim_topics))) + .add_attribute("claim_topics", format!("{claim_topics:?}"))) } pub fn remove_trusted_issuer( @@ -367,7 +367,7 @@ pub mod execute { .add_message(msg) .add_attribute("action", "update_issuer_claim_topics") .add_attribute("issuer", issuer.to_string()) - .add_attribute("claim_topics", format!("{:?}", claim_topics))) + .add_attribute("claim_topics", format!("{claim_topics:?}"))) } } @@ -877,7 +877,7 @@ mod tests { vec![ attr("action", "add_trusted_issuer"), attr("issuer", new_issuer.to_string()), - attr("claim_topics", format!("{:?}", claim_topics)), + attr("claim_topics", format!("{claim_topics:?}")), ] ); } @@ -988,7 +988,7 @@ mod tests { vec![ attr("action", "update_issuer_claim_topics"), attr("issuer", issuer_to_update.to_string()), - attr("claim_topics", format!("{:?}", new_claim_topics)), + attr("claim_topics", format!("{new_claim_topics:?}")), ] ); } diff --git a/contracts/trusted-issuers/src/contract.rs b/contracts/trusted-issuers/src/contract.rs index 189697f..3c05e88 100644 --- a/contracts/trusted-issuers/src/contract.rs +++ b/contracts/trusted-issuers/src/contract.rs @@ -134,7 +134,7 @@ pub mod execute { Ok(Response::new() .add_attribute("action", "add_trusted_issuer") .add_attribute("issuer", issuer.to_string()) - .add_attribute("claim_topics", format!("{:?}", claim_topics))) + .add_attribute("claim_topics", format!("{claim_topics:?}"))) } pub fn update_trusted_issuer( @@ -155,7 +155,7 @@ pub mod execute { Ok(Response::new() .add_attribute("action", "updated_trusted_issuer") .add_attribute("issuer", issuer.to_string()) - .add_attribute("claim_topics", format!("{:?}", claim_topics))) + .add_attribute("claim_topics", format!("{claim_topics:?}"))) } pub fn remove_trusted_issuer(deps: DepsMut, issuer: Addr) -> Result { diff --git a/integration-tests/tests/cw3643-full-test.js b/integration-tests/tests/cw3643-full-test.js index 6874e8c..5aab752 100644 --- a/integration-tests/tests/cw3643-full-test.js +++ b/integration-tests/tests/cw3643-full-test.js @@ -3,12 +3,12 @@ const assert = require('assert'); describe("CW3643 Token", function() { this.timeout(60000); // Increase timeout to 60 seconds for the entire test suite - + let owner, ownerClient; let recipient, recipientClient; let issuer, issuerClient; let contracts = {}; - + before(async function() { ({ account: owner, client: ownerClient } = await setupWallet()); ({ account: recipient, client: recipientClient } = await setupWallet()); @@ -17,10 +17,18 @@ describe("CW3643 Token", function() { // Deploy contracts in the correct order contracts.ownerRoles = await deployContract(ownerClient, owner.address, "owner_roles", { owner: owner.address }); contracts.agentRoles = await deployContract(ownerClient, owner.address, "agent_roles", { owner: owner.address }); - contracts.trustedIssuers = await deployContract(ownerClient, owner.address, "trusted_issuers", { owner_roles_address: contracts.ownerRoles.contractAddress }); - contracts.claimTopics = await deployContract(ownerClient, owner.address, "claim_topics", { owner_roles_address: contracts.ownerRoles.contractAddress }); - contracts.onChainId = await deployContract(ownerClient, owner.address, "on_chain_id", { owner: owner.address, trusted_issuer_addr: contracts.trustedIssuers.contractAddress }); - contracts.complianceRegistry = await deployContract(ownerClient, owner.address, "compliance_registry", { owner_roles_address: contracts.ownerRoles.contractAddress }); + contracts.trustedIssuers = await deployContract(ownerClient, owner.address, "trusted_issuers", { + owner_roles_address: contracts.ownerRoles.contractAddress + }); + contracts.claimTopics = await deployContract(ownerClient, owner.address, "claim_topics", { + owner_roles_address: contracts.ownerRoles.contractAddress + }); + contracts.onChainId = await deployContract(ownerClient, owner.address, "on_chain_id", { owner: owner.address, + trusted_issuer_addr: contracts.trustedIssuers.contractAddress + }); + contracts.complianceRegistry = await deployContract(ownerClient, owner.address, "compliance_registry", { + owner_roles_address: contracts.ownerRoles.contractAddress, + }); contracts.complianceClaims = await deployContract(ownerClient, owner.address, "compliance_claims", { identity_address: contracts.onChainId.contractAddress, owner_roles_address: contracts.ownerRoles.contractAddress, @@ -254,7 +262,7 @@ describe("CW3643 Token", function() { amount: null } }); - + assert.strictEqual(response, true); // Should be true because owner is still in CA }); @@ -268,7 +276,7 @@ describe("CW3643 Token", function() { amount: null } }); - + assert.strictEqual(response, true); // Should be true because issuer is still in CA }); @@ -288,7 +296,7 @@ describe("CW3643 Token", function() { it("should add a compliance module", async function() { await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - add_compliance_module: { + add_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceCountry.contractAddress, module_name: "CountryRestrictionCompliance" @@ -359,7 +367,7 @@ describe("CW3643 Token", function() { it("should fail transfer due to compliance check", async function() { const transferAmount = "1000"; - + // Get initial balance of recipient const initialBalance = await queryContract(ownerClient, contracts.cw20Base.contractAddress, { balance: { @@ -541,7 +549,7 @@ describe("CW3643 Token", function() { it("should remove compliance module", async function() { await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - remove_compliance_module: { + remove_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceCountry.contractAddress } @@ -602,7 +610,7 @@ describe("CW3643 Token", function() { const existingClaimTopics = await queryContract(ownerClient, contracts.claimTopics.contractAddress, { get_claims_for_token: { token_addr: contracts.cw20Base.contractAddress } }); - + for (const topic of existingClaimTopics) { await executeContract(ownerClient, owner.address, contracts.claimTopics.contractAddress, { remove_claim_topic_for_token: { @@ -622,7 +630,7 @@ describe("CW3643 Token", function() { // Add compliance modules await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - add_compliance_module: { + add_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceClaims.contractAddress, module_name: "ClaimsCompliance" @@ -630,7 +638,7 @@ describe("CW3643 Token", function() { }); await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - add_compliance_module: { + add_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceCountry.contractAddress, module_name: "CountryRestrictionCompliance" @@ -789,13 +797,13 @@ describe("CW3643 Token", function() { after(async function() { // Remove compliance modules await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - remove_compliance_module: { + remove_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceCountry.contractAddress } }); await executeContract(ownerClient, owner.address, contracts.complianceRegistry.contractAddress, { - remove_compliance_module: { + remove_compliance_module: { token_address: contracts.cw20Base.contractAddress, module_address: contracts.complianceClaims.contractAddress } diff --git a/scripts/balance.py b/scripts/balance.py new file mode 100755 index 0000000..4c390a0 --- /dev/null +++ b/scripts/balance.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import config +import sys +from common import execute_contract, get_key_address, query_contract + +############################# +# Import Core Variables # +############################# + +CONTRACTS = config.CONTRACTS + +############# +# Functions # +############# + +def balance_of(token_address, user_key): + print(f"Checking balance of {user_key} on CW20 {token_address}") + balance = query_contract( + token_address, + {"balance": {"address": get_key_address(user_key)}}, + ) + print(f"Balance: {balance}") + +######## +# Call # +######## +if __name__== "__main__": + if len(sys.argv) > 2: + user_key = sys.argv[1] + token_address = sys.argv[2] + balance_of(token_address, user_key) + elif len(sys.argv) > 1: + user_key = sys.argv[1] + print("Assuming usage of default contract") + balance_of(CONTRACTS["cw20_base_address"], user_key) + else: + print("Usage: ./balance user_key [token_address]") diff --git a/scripts/common.py b/scripts/common.py new file mode 100644 index 0000000..55a0b09 --- /dev/null +++ b/scripts/common.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 + +import subprocess +import json +import requests + +############################# +# Core Deployment Variables # +############################# + +KIICHAIN = "kiichaind" +RPC_URL = "https://rpc.uno.sentry.testnet.v3.kiivalidator.com" +LCD_NODE = "https://lcd.uno.sentry.testnet.v3.kiivalidator.com" +CHAIN_ID = "oro_1336-1" +TXFLAG = [ + "--gas", + "auto", + "--gas-adjustment", + "1.2", + "--gas-prices", + "500000000000akii", + "--keyring-backend", + "test", + "--node", + RPC_URL, + "--chain-id", + CHAIN_ID, + "-y", + "-o", + "json", +] + +################## +# Util functions # +################## + +# Define all the helper functions +def run_cmd(cmd): + result = subprocess.run(cmd, capture_output=True, text=True) + return result.stdout, result.stderr + +# Get the key address from the key name +def get_key_address(key_name): + # Build the command to get the key address + cmd = [KIICHAIN, "keys", "show", key_name, "--output", "json"] + + # Run the command + result, err = run_cmd(cmd) + + # If there is an error, we raise + if err: + raise Exception(f"Failed to get key address: {err}") + + # Parse the result and return the address + key_info = json.loads(result) + return key_info["address"] + +# Check check a tx until a result is returned +def check_tx_until_result(tx_hash): + while True: + # We can check the TX using the APIs + res = requests.get(f"{LCD_NODE}/cosmos/tx/v1beta1/txs/{tx_hash}") + + # If the response is 404, this means the TX is not yet processed + if res.status_code == 404: + continue + + # If the response is different than 200, we raise + if res.status_code != 200: + raise Exception(f"Failed to check tx {tx_hash}: {res.text}") + + # Reaching this point means the TX is processed + result = res.json() + code = result["tx_response"]["code"] + + # If the code isn't 0 we raise + if code != 0: + raise Exception(f"Transaction {tx_hash} failed with code {code}: {result}") + + # Reaching this point we can return the result + return result + +# Store contract stores a contract and return the TX hash +def store_contract(path, from_key): + # Build the cmd for deploying the contract + cmd = [KIICHAIN, "tx", "wasm", "store", path, "--from", from_key] + TXFLAG + + # Run the command to store the contract + result, err = run_cmd(cmd) + if len(err.splitlines()) > 1 or "gas estimate" not in err.splitlines()[0]: + raise Exception(f"Failed to store 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 store contract: {result}") + + # If all is fine we wait for the tx to be processed + result = check_tx_until_result(tx_hash) + + # From the result we can get the code id + code_id = next( + attr["value"] + for event in result["tx_response"]["events"] + for attr in event["attributes"] + if attr["key"] == "code_id" + ) + + # Return the code id + return code_id + +# Instantiate contract instantiates a contract with the given code ID and init message +def instantiate_contract(code_id, init_msg, label, from_key): + # Get the key address from the key name + key_address = get_key_address(from_key) + + # Build the cmd for instantiating the contract + cmd = [ + KIICHAIN, + "tx", + "wasm", + "instantiate", + code_id, + json.dumps(init_msg), + "--label", + label, + "--admin", + key_address, + "--from", + from_key, + ] + TXFLAG + + # Run the command to instantiate the contract + result, err = run_cmd(cmd) + if len(err.splitlines()) > 1 or "gas estimate" not in err.splitlines()[0]: + raise Exception(f"Failed to instantiate 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 instantiate contract: {result}") + + # If all is fine we wait for the tx to be processed + result = check_tx_until_result(tx_hash) + + # Get the contract address from the result + contract_address = next( + attr["value"] + for event in result["tx_response"]["events"] + for attr in event["attributes"] + if attr["key"] == "_contract_address" + ) + + # Return the contract address + return contract_address + +# Execute contract executes a contract with the given message +def execute_contract(contract_address, msg, from_key): + # Build the command to execute the contract + cmd = [ + KIICHAIN, + "tx", + "wasm", + "execute", + contract_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 + return check_tx_until_result(tx_hash) + +# Query contract queries a contract with the given query message +def query_contract(contract_address, query_msg): + # Build the command to query the contract + cmd = [ + KIICHAIN, + "query", + "wasm", + "contract-state", + "smart", + contract_address, + json.dumps(query_msg), + "--node", RPC_URL, + "-o", "json" + ] + + # Run the command + result, err = run_cmd(cmd) + if err: + raise Exception(f"Failed to query contract: {err}") + + # Parse and return the result + return json.loads(result) + +def query_contract_fail_with(contract_address, query_msg, expected_err): + # Build the command to query the contract + cmd = [ + KIICHAIN, + "query", + "wasm", + "contract-state", + "smart", + contract_address, + json.dumps(query_msg), + "--node", RPC_URL, + "-o", "json" + ] + + # Run the command + result, err = run_cmd(cmd) + if err: + if expected_err not in err: + raise Exception(f"Contract failed with unexpected error: {err}") + return result, True + + # Parse and return the result + return result, False diff --git a/scripts/compliance_setup.py b/scripts/compliance_setup.py new file mode 100755 index 0000000..76d98fd --- /dev/null +++ b/scripts/compliance_setup.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import sys +import config +from common import execute_contract, query_contract + +############################# +# Import Core Variables # +############################# + +OWNER_KEY_NAME = config.OWNER_KEY_NAME +OWNER_KEY_ADDRESS = config.OWNER_KEY_ADDRESS +CONTRACTS = config.CONTRACTS + +############# +# Functions # +############# + +def add_compliance_to_token(token_address): + # For now we add the compliances we have to the registry we have + # Claims compliance + print(f"Adding claim compliance to token {token_address}...") + execute_contract( + CONTRACTS["compliance_registry_address"], + { + "add_compliance_module": { + "token_address": token_address, + "module_address": CONTRACTS["compliance_claims_address"], + "module_name": "ClaimsCompliance" + } + }, + OWNER_KEY_NAME, + ) + print("Claim compliance added") + + # Country compliance + print(f"Adding country compliance to token {token_address}...") + execute_contract( + CONTRACTS["compliance_registry_address"], + { + "add_compliance_module": { + "token_address": token_address, + "module_address": CONTRACTS["compliance_country_restriction_address"], + "module_name": "ClaimsCompliance" + } + }, + OWNER_KEY_NAME, + ) + print("Country compliance added") + +######## +# Call # +######## +if __name__== "__main__": + if len(sys.argv) > 1: + token_address = sys.argv[1] + print(f"Setting up compliance for token: {token_address}") + add_compliance_to_token(token_address) + else: + token_address = CONTRACTS["cw20_base_address"] + print(f"Assuming usage of default token address: {token_address}") + add_compliance_to_token(token_address) diff --git a/scripts/config.py b/scripts/config.py new file mode 100644 index 0000000..ec36d5a --- /dev/null +++ b/scripts/config.py @@ -0,0 +1,24 @@ +from common import get_key_address + +############################# +# Core Deployment Variables # +############################# + +# Deployment +OWNER_KEY_NAME = "rwa" +OWNER_KEY_ADDRESS = get_key_address(OWNER_KEY_NAME) +CONTRACTS = { + "owner_roles_address": "kii1hez8n9mnljtca28xrg4a54p4cdharlsg0vku0008ng64ldgfapdqsqctag", + "agent_roles_address": "kii14tzdqsgsdfcgxy0zu2vqcaj347lv5xpelpadusrspnkxddc53fhq9vc0h8", + "trusted_issuers_address": "kii17xsl3q2p3elhh747r3ttn08j2p95jsd3c7rfmuc5rws5a20u9kqqrrx0nd", + "claim_topics_address": "kii17k6uthn6ymd3ta25glx6qpa2sfz2hkt5a22lynzdm7xgk7kklckqc4gytm", + "on_chain_id_address": "kii1y8s38zn7ry7xc95ej2z08eq520y4v9qdsysfgkfwelhh635e4t2qq8xxp5", + "compliance_registry_address": "kii1dpws8lmu2l4awdau6zhezxm97tshu427aulr2xwp7uarpd8jqu7srjz2gm", + "compliance_claims_address": "kii12n3frtnx8mh2vpvzk7mqkr6yqkfe77339c7uakzqqa4pv46zdr9qt020h3", + "compliance_country_restriction_address": "kii1k4j6gr75k23tvqjdw9zvtrdxh7pxtexy5pdk7xjmwf385msx75fsz470kz", + "cw20_base_address": "kii1zvjy36ysunq56dhgyxggp5gwrymxjs95twj5lgcpqujftppn739s83aym4", +} + +# Management +TRUSTED_ISSUER_KEY_NAME = "trusted_issuer" +TRUSTED_ISSUER_KEY_ADDRESS = get_key_address(TRUSTED_ISSUER_KEY_NAME) diff --git a/scripts/deploy.py b/scripts/deploy.py new file mode 100755 index 0000000..265191e --- /dev/null +++ b/scripts/deploy.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +from common import instantiate_contract, store_contract + +import config + +############################# +# Core Deployment Variables # +############################# + +# Define the main variables for the deployment +KEY_NAME = config.OWNER_KEY_NAME +KEY_ADDRESS = config.OWNER_KEY_ADDRESS + +############################ +# Contract Deployment Zone # +############################ + +# Owner Roles Contract +print("Deploying Owner Roles contract...") +owner_roles = store_contract("artifacts/owner_roles.wasm", KEY_NAME) +print(f"Owner roles stored with code ID {owner_roles}") +owner_roles_init_msg = { + "owner": KEY_ADDRESS, +} +owner_roles_address = instantiate_contract( + owner_roles, owner_roles_init_msg, "OwnerRoles", KEY_NAME +) +print(f"Owner roles contract instantiated at {owner_roles_address}") + +# Agent roles +print("Deploying Agent Roles contract...") +agent_roles = store_contract("artifacts/agent_roles.wasm", KEY_NAME) +print(f"Agent roles stored with code ID {agent_roles}") +agent_roles_init_msg = { + "owner": KEY_ADDRESS, +} +agent_roles_address = instantiate_contract( + agent_roles, agent_roles_init_msg, "AgentRoles", KEY_NAME +) +print(f"Agent roles contract instantiated at {agent_roles_address}") + +# trustedIssuers +print("Deploying Trusted Issuers contract...") +trusted_issuers = store_contract("artifacts/trusted_issuers.wasm", KEY_NAME) +print(f"Trusted issuers stored with code ID {trusted_issuers}") +trusted_issuers_init_msg = { + "owner_roles_address": owner_roles_address, +} +trusted_issuers_address = instantiate_contract( + trusted_issuers, trusted_issuers_init_msg, "TrustedIssuers", KEY_NAME +) +print(f"Trusted issuers contract instantiated at {trusted_issuers_address}") + +# Claim Topics +print("Deploying Claim Topics contract...") +claim_topics = store_contract("artifacts/claim_topics.wasm", KEY_NAME) +print(f"Claim topics stored with code ID {claim_topics}") +claim_topics_init_msg = { + "owner_roles_address": owner_roles_address, +} +claim_topics_address = instantiate_contract( + claim_topics, claim_topics_init_msg, "ClaimTopics", KEY_NAME +) +print(f"Claim topics contract instantiated at {claim_topics_address}") + +# On Chain ID +print("Deploying On Chain ID contract...") +on_chain_id = store_contract("artifacts/on_chain_id.wasm", KEY_NAME) +print(f"On Chain ID stored with code ID {on_chain_id}") +on_chain_id_init_msg = { + "trusted_issuer_addr": trusted_issuers_address, + "owner": KEY_ADDRESS, +} +on_chain_id_address = instantiate_contract( + on_chain_id, on_chain_id_init_msg, "OnChainID", KEY_NAME +) +print(f"On Chain ID contract instantiated at {on_chain_id_address}") + +# Compliance registry +print("Deploying Compliance Registry contract...") +compliance_registry = store_contract("artifacts/compliance_registry.wasm", KEY_NAME) +print(f"Compliance Registry stored with code ID {compliance_registry}") +compliance_registry_init_msg = { + "owner_roles_address": owner_roles_address, +} +compliance_registry_address = instantiate_contract( + compliance_registry, compliance_registry_init_msg, "ComplianceRegistry", KEY_NAME +) +print(f"Compliance Registry contract instantiated at {compliance_registry_address}") + +# Compliance claims +print("Deploying Compliance Claims contract...") +compliance_claims = store_contract("artifacts/compliance_claims.wasm", KEY_NAME) +print(f"Compliance Claims stored with code ID {compliance_claims}") +compliance_claims_init_msg = { + "identity_address": on_chain_id_address, + "owner_roles_address": owner_roles_address, + "claim_topics_address": claim_topics_address, +} +compliance_claims_address = instantiate_contract( + compliance_claims, compliance_claims_init_msg, "ComplianceClaims", KEY_NAME +) +print(f"Compliance Claims contract instantiated at {compliance_claims_address}") + +# Compliance country restriction +print("Deploying Compliance Country Restriction contract...") +compliance_country_restriction = store_contract( + "artifacts/compliance_country_restriction.wasm", KEY_NAME +) +print( + f"Compliance Country Restriction stored with code ID {compliance_country_restriction}" +) +compliance_country_restriction_init_msg = { + "identity_address": on_chain_id_address, + "owner_roles_address": owner_roles_address, +} +compliance_country_restriction_address = instantiate_contract( + compliance_country_restriction, + compliance_country_restriction_init_msg, + "ComplianceCountryRestriction", + KEY_NAME, +) +print( + f"Compliance Country Restriction contract instantiated at {compliance_country_restriction_address}" +) + +# CW20 base for test token +print("Deploying CW20 Base contract...") +cw20_base = store_contract("artifacts/cw20_base.wasm", KEY_NAME) +print(f"CW20 Base stored with code ID {cw20_base}") +cw20_base_init_msg = { + "token_info": { + "name": "Test Token", + "symbol": "TEST", + "decimals": 6, + "initial_balances": [{"address": KEY_ADDRESS, "amount": "1000000"}], + }, + "registries": { + "compliance_address": compliance_registry_address, + }, +} +cw20_base_address = instantiate_contract( + cw20_base, cw20_base_init_msg, "CW20Base", KEY_NAME +) +print(f"CW20 Base contract instantiated at {cw20_base_address}") diff --git a/scripts/manager_setup.py b/scripts/manager_setup.py new file mode 100755 index 0000000..d6386a7 --- /dev/null +++ b/scripts/manager_setup.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import config +from common import execute_contract, query_contract, query_contract_fail_with + +############################# +# Import Core Variables # +############################# + +TRUSTED_ISSUER_KEY_NAME = config.TRUSTED_ISSUER_KEY_NAME +TRUSTED_ISSUER_KEY_ADDRESS = config.TRUSTED_ISSUER_KEY_ADDRESS +OWNER_KEY_NAME = config.OWNER_KEY_NAME +OWNER_KEY_ADDRESS = config.OWNER_KEY_ADDRESS +CONTRACTS = config.CONTRACTS + +######## +# Call # +######## + +# Setup the owner roles +has_issuers_registry_manager = query_contract( + CONTRACTS["owner_roles_address"], + {"is_owner": {"owner": OWNER_KEY_ADDRESS, "role": "issuers_registry_manager"}}, +) +if not has_issuers_registry_manager["data"]["is_owner"]: + print( + f"Key {OWNER_KEY_NAME} is not an issuers registry manager. Creating a new role..." + ) + + # Create a new role for the owner + res = execute_contract( + CONTRACTS["owner_roles_address"], + { + "add_owner_role": { + "owner": OWNER_KEY_ADDRESS, + "role": "issuers_registry_manager", + } + }, + OWNER_KEY_NAME, + ) + + print("Role created") +else: + print(f"Key {OWNER_KEY_NAME} is an issuers registry manager.") + +# Setup the claim registry manager +has_claim_registry_manager = query_contract( + CONTRACTS["owner_roles_address"], + {"is_owner": {"owner": OWNER_KEY_ADDRESS, "role": "claim_registry_manager"}}, +) +if not has_claim_registry_manager["data"]["is_owner"]: + print(f"Key {OWNER_KEY_NAME} is not a claim registry manager. Creating a new role...") + + # Create a new role for the owner + res = execute_contract( + CONTRACTS["owner_roles_address"], + { + "add_owner_role": { + "owner": OWNER_KEY_ADDRESS, + "role": "claim_registry_manager", + } + }, + OWNER_KEY_NAME, + ) + + print("Role created") +else: + print(f"Key {OWNER_KEY_NAME} is a claim registry manager.") + +# Setup the compliance manager +has_compliance_manager = query_contract( + CONTRACTS["owner_roles_address"], + {"is_owner": {"owner": OWNER_KEY_ADDRESS, "role": "compliance_manager"}}, +) +if not has_compliance_manager["data"]["is_owner"]: + print(f"Key {OWNER_KEY_NAME} is not a compliance manager. Creating a new role...") + + # Create a new role for the owner + res = execute_contract( + CONTRACTS["owner_roles_address"], + { + "add_owner_role": { + "owner": OWNER_KEY_ADDRESS, + "role": "compliance_manager", + } + }, + OWNER_KEY_NAME, + ) + + print("Role created") +else: + print(f"Key {OWNER_KEY_NAME} is a compliance registry manager.") + +# Check if a address is a trusted issuer +is_trusted_issuer = query_contract( + CONTRACTS["trusted_issuers_address"], + {"is_trusted_issuer": {"issuer": TRUSTED_ISSUER_KEY_ADDRESS}}, +) +if not is_trusted_issuer["data"]: + print( + f"Key {TRUSTED_ISSUER_KEY_NAME} is not a trusted issuer. Creating a new trusted issuer..." + ) + + # Create a new trusted issuer + res = execute_contract( + CONTRACTS["trusted_issuers_address"], + { + "add_trusted_issuer": { + "issuer": TRUSTED_ISSUER_KEY_ADDRESS, + "claim_topics": ["1"], + } + }, + OWNER_KEY_NAME, + ) + + print("Trusted issuer created") +else: + print (f"Key {TRUSTED_ISSUER_KEY_NAME} is a trusted issuer.") + +# Add a claim topic to the CW20 token +claim_topics_for_token = query_contract( + CONTRACTS["claim_topics_address"], + {"get_claims_for_token": {"token_addr": CONTRACTS["cw20_base_address"] + }}, +) +if len(claim_topics_for_token["data"]) == 0: + print( + f"Token {CONTRACTS['cw20_base_address']} has no claim topics. Adding a claim topic..." + ) + + # Add a claim topic to the token + res = execute_contract( + CONTRACTS["claim_topics_address"], + { + "add_claim_topic_for_token": { + "token_addr": CONTRACTS["cw20_base_address"], + "topic": "1", + } + }, + OWNER_KEY_NAME, + ) + + print("Claim topic added") +else: + print(f"Token {CONTRACTS['cw20_base_address']} has claim topics") + +# Add identity to trusted issuer + +_, inexistent = query_contract_fail_with( + CONTRACTS["on_chain_id_address"], + {"get_identity": {"identity_owner": TRUSTED_ISSUER_KEY_ADDRESS}}, + "not found: query wasm contract failed" + ) +if inexistent: + print( + f"Key {TRUSTED_ISSUER_KEY_NAME} has no identity. Creating a new Brazilian identity..." + ) + + # Create a new identity for the owner + res = execute_contract( + CONTRACTS["on_chain_id_address"], + { + "add_identity": { + "country": "BR" + } + }, + TRUSTED_ISSUER_KEY_NAME, + ) + + print("Identity created") +else: + print(f"Key {TRUSTED_ISSUER_KEY_NAME} has an identity.") diff --git a/scripts/transfer.py b/scripts/transfer.py new file mode 100755 index 0000000..49111c7 --- /dev/null +++ b/scripts/transfer.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import config +import sys +from common import execute_contract, get_key_address, query_contract + +############################# +# Import Core Variables # +############################# + +CONTRACTS = config.CONTRACTS + +############# +# Functions # +############# + +def transfer(sender, receiver, quantity): + print(f"{sender} is transferring {quantity} to {receiver}. Both need to have claims and compliance") + execute_contract( + CONTRACTS["cw20_base_address"], + { + "transfer": { + "recipient" : get_key_address(receiver), + "amount": quantity + } + }, + sender, + ) + print("Send completed") + +######## +# Call # +######## +if __name__== "__main__": + if len(sys.argv) > 3: + sender = sys.argv[1] + receiver = sys.argv[2] + quantity = sys.argv[3] + transfer(sender, receiver, quantity) + else: + print("Usage: ./transfer sender_key_name receiver_key_name quantity") diff --git a/scripts/user_setup.py b/scripts/user_setup.py new file mode 100755 index 0000000..56a7ad2 --- /dev/null +++ b/scripts/user_setup.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +import config +import sys +from common import execute_contract, get_key_address, query_contract, query_contract_fail_with + +# User and trusted issuer needs to have balance in account + +############################# +# Import Core Variables # +############################# + +TRUSTED_ISSUER_KEY_NAME = config.TRUSTED_ISSUER_KEY_NAME +TRUSTED_ISSUER_KEY_ADDRESS = config.TRUSTED_ISSUER_KEY_ADDRESS +CONTRACTS = config.CONTRACTS + +############# +# Functions # +############# + +def setup_user(user_name): + USER_KEY_NAME = user_name + USER_KEY_ADDRESS = get_key_address(USER_KEY_NAME) + + # 1. User creates own identity + # Checks if identity exists + _, inexistent = query_contract_fail_with( + CONTRACTS["on_chain_id_address"], + {"get_identity": {"identity_owner": USER_KEY_ADDRESS}}, + "not found: query wasm contract failed" + ) + if inexistent: + # Create a new identity for the owner + print(f"Key {USER_KEY_NAME} has no identity. Creating a new Brazilian identity...") + execute_contract( + CONTRACTS["on_chain_id_address"], + { + "add_identity": { + "country": "BR" + } + }, + USER_KEY_NAME, + ) + print("Identity created") + else: + print(f"Key {USER_KEY_NAME} has an identity.") + + # 2. User gives permission for trusted issuer to add claims + # Check if permission already exists + _, inexistent = query_contract_fail_with( + CONTRACTS["on_chain_id_address"], + {"get_key": { + "key_owner": TRUSTED_ISSUER_KEY_ADDRESS, + "key_type" : "ClaimSignerKey", + "identity_owner": USER_KEY_ADDRESS + }}, + "Key not found for owner" + ) + if inexistent: + # Give permission to trusted issuer to add claims + print(f"Key {TRUSTED_ISSUER_KEY_NAME} has no permission to add claims to key {USER_KEY_NAME}. Adding permission...") + execute_contract( + CONTRACTS["on_chain_id_address"], + { + "add_key": { + "key_owner": TRUSTED_ISSUER_KEY_ADDRESS, + "key_type" : "ClaimSignerKey", + "identity_owner": USER_KEY_ADDRESS + } + }, + USER_KEY_ADDRESS, + ) + print("Permission created") + else: + print(f"Key {TRUSTED_ISSUER_KEY_NAME} has permission to add claims to key {USER_KEY_NAME}.") + + # 3. Trusted issuer creates a claim for the user + # Check if claim already exists + has_claim = query_contract( + CONTRACTS["on_chain_id_address"], + {"verify_claim": { + "claim_id": "1", + "identity_owner": USER_KEY_ADDRESS + }}, + ) + if not has_claim["data"]: + # Give permission to trusted issuer to add claims + print(f"Key {USER_KEY_NAME} has no claims to topic 1. Adding claim...") + execute_contract( + CONTRACTS["on_chain_id_address"], + { + "add_claim": { + "claim" : { + "topic": "1", + "issuer": TRUSTED_ISSUER_KEY_ADDRESS, + "data": "", + "uri": "" + }, + "identity_owner": USER_KEY_ADDRESS + } + }, + TRUSTED_ISSUER_KEY_NAME, + ) + print("Claim created") + else: + print(f"Key {USER_KEY_NAME} has claim to topic 1.") + +######## +# Call # +######## +if __name__== "__main__": + if len(sys.argv) > 1: + name = sys.argv[1] + print(f"Setting up user key: {name}") + setup_user(name) + else: + print("Please provide the user key name when calling the script")