diff --git a/contracts/treasury/contract.rs b/contracts/treasury/contract.rs new file mode 100644 index 0000000..3297091 --- /dev/null +++ b/contracts/treasury/contract.rs @@ -0,0 +1,67 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + coin, to_binary, Addr, BankMsg, Binary, Decimal, Deps, DepsMut, DistributionMsg, Env, + MessageInfo, QuerierWrapper, Response, StakingMsg, StdError, StdResult, Uint128, WasmMsg, +}; + +use cw2::set_contract_version; +use cw20_base::allowances::{ + execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from, + execute_transfer_from, query_allowance, +}; +use cw20_base::contract::{ + execute_burn, execute_mint, execute_send, execute_transfer, query_balance, query_token_info, +}; +use cw20_base::state::{MinterData, TokenInfo, TOKEN_INFO}; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{Supply, CLAIMS}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw20-staking"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[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)?; + + // ensure the validator is registered + let vals = deps.querier.query_all_validators()?; + if !vals.iter().any(|v| v.address == msg.validator) { + return Err(ContractError::NotInValidatorSet { + validator: msg.validator, + }); + } + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + // these all come from cw20-base to implement the cw20 standard + ExecuteMsg::transfer_treasury_fungible_token { recipient, amount } => { + Ok(execute_transfer(deps, env, info, recipient, amount)?) + } + + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::get_treasury_fungible_token_balance { address } => to_binary(&query_balance(deps, address)?), + } +} \ No newline at end of file diff --git a/contracts/treasury/error.rs b/contracts/treasury/error.rs new file mode 100644 index 0000000..cf0dcaf --- /dev/null +++ b/contracts/treasury/error.rs @@ -0,0 +1,46 @@ +use cosmwasm_std::{StdError, Uint128}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Validator '{validator}' not in current validator set")] + NotInValidatorSet { validator: String }, + + #[error("No {denom} tokens sent")] + EmptyBalance { denom: String }, + + #[error("Insufficient balance in contract to process claim")] + BalanceTooSmall {}, + + #[error("Cannot set to own account")] + CannotSetOwnAccount {}, + + #[error("Invalid zero amount")] + InvalidZeroAmount {}, + + #[error("Duplicate initial balance addresses")] + DuplicateInitialBalanceAddresses {}, +} + +impl From for ContractError { + fn from(err: cw20_base::ContractError) -> Self { + match err { + cw20_base::ContractError::Std(error) => ContractError::Std(error), + cw20_base::ContractError::Unauthorized {} => ContractError::Unauthorized {}, + cw20_base::ContractError::CannotSetOwnAccount {} => { + ContractError::CannotSetOwnAccount {} + } + cw20_base::ContractError::InvalidZeroAmount {} => ContractError::InvalidZeroAmount {}, + // This should never happen, as this contract doesn't use logo + cw20_base::ContractError::DuplicateInitialBalanceAddresses {} => { + ContractError::DuplicateInitialBalanceAddresses {} + } + } + } +} \ No newline at end of file diff --git a/contracts/treasury/lib.rs b/contracts/treasury/lib.rs new file mode 100644 index 0000000..ad3f9a5 --- /dev/null +++ b/contracts/treasury/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +mod error; +pub mod msg; + +pub use crate::error::ContractError; \ No newline at end of file diff --git a/contracts/treasury/msg.rs b/contracts/treasury/msg.rs new file mode 100644 index 0000000..15cc0b0 --- /dev/null +++ b/contracts/treasury/msg.rs @@ -0,0 +1,44 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Binary, Coin, Decimal, Uint128}; +use cw20::Expiration; +pub use cw_controllers::ClaimsResponse; +use cw_utils::Duration; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + /// name of the derivative token + pub name: String, + /// symbol / ticker of the derivative token + pub symbol: String, + /// decimal places of the derivative token (for UI) + pub decimals: u8, + + /// This is the validator that all tokens will be bonded to + pub validator: String, + /// This is the unbonding period of the native staking module + /// We need this to only allow claims to be redeemed after the money has arrived + pub unbonding_period: Duration, + + /// this is how much the owner takes as a cut when someone unbonds + pub exit_tax: Decimal, + /// This is the minimum amount we will pull out to reinvest, as well as a minimum + /// that can be unbonded (to avoid needless staking tx) + pub min_withdrawal: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions + transfer_treasury_fungible_token { recipient: String, amount: Uint128 }, + +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Implements CW20. Returns the current balance of the given address, 0 if unset. + get_treasury_fungible_token_balance { address: String }, +} \ No newline at end of file diff --git a/interact/tests/suite1.rs b/interact/tests/suite1.rs index 8ebb664..314a65e 100644 --- a/interact/tests/suite1.rs +++ b/interact/tests/suite1.rs @@ -76,7 +76,7 @@ async fn check_block_number() { } /// by requesting the full node, checks whether the account given by the config has enough native token to pay gas fee -#[ignore] + #[tokio::test] async fn check_account() { //let _config = Config::read_from_env(); @@ -184,7 +184,6 @@ async fn test_execute_increment_fail() { // deliver_tx failed: TxResult { code: Err(5), data: None, log: Log("failed to execute message; message index: 0: Unauthorized: execute wasm contract failed"), info: Info(""), gas_wanted: Gas(2000000), gas_used: Gas(136249), events: [Event { type_str: "coin_spent", attributes: [Tag { key: Key("spender"), value: Value("wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf") }, Tag { key: Key("amount"), value: Value("2000000umlg") }] }, Event { type_str: "coin_received", attributes: [Tag { key: Key("receiver"), value: Value("wasm17xpfvakm2amg962yls6f84z3kell8c5l69j4zk") }, Tag { key: Key("amount"), value: Value("2000000umlg") }] }, Event { type_str: "transfer", attributes: [Tag { key: Key("recipient"), value: Value("wasm17xpfvakm2amg962yls6f84z3kell8c5l69j4zk") }, Tag { key: Key("sender"), value: Value("wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf") }, Tag { key: Key("amount"), value: Value("2000000umlg") }] }, Event { type_str: "message", attributes: [Tag { key: Key("sender"), value: Value("wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf") }] }, Event { type_str: "tx", attributes: [Tag { key: Key("fee"), value: Value("2000000umlg") }] }, Event { type_str: "tx", attributes: [Tag { key: Key("acc_seq"), value: Value("wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf/5") }] }, Event { type_str: "tx", attributes: [Tag { key: Key("signature"), value: Value("dHGDLpzY8zHIO/E1K6MHTNWkbF5RMlYbYzTzlIqpjzgfUfk7EO7L0hC7mHoJO+9lQJhV01JJMnAWDQToe+RogA==") }] }], codespace: Codespace("wasm") } } -#[ignore] #[tokio::test] async fn test_execute_increment() { let mnemonic = "coyote electric million purchase tennis skin quiz inside helmet call glimpse pulse turkey hint maze iron festival run bomb regular legend prepare service angry".to_string(); @@ -247,7 +246,6 @@ async fn test_execute_reset() { // [{"events":[{"type":"coin_received","attributes":[{"key":"receiver","value":"wasm1rpfxxy379eq2lq8wjz0lcke9ql49p5uzx2246vx6pml7yvd954tstdaaae"},{"key":"amount","value":"10000umlg"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf"},{"key":"amount","value":"10000umlg"}]},{"type":"execute","attributes":[{"key":"_contract_address","value":"wasm1rpfxxy379eq2lq8wjz0lcke9ql49p5uzx2246vx6pml7yvd954tstdaaae"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmwasm.wasm.v1.MsgExecuteContract"},{"key":"module","value":"wasm"},{"key":"sender","value":"wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"wasm1rpfxxy379eq2lq8wjz0lcke9ql49p5uzx2246vx6pml7yvd954tstdaaae"},{"key":"sender","value":"wasm1quzyfdgzw42aelcdkrw2v8vnfdxsk9jkl7a4qf"},{"key":"amount","value":"10000umlg"}]},{"type":"wasm","attributes":[{"key":"_contract_address","value":"wasm1rpfxxy379eq2lq8wjz0lcke9ql49p5uzx2246vx6pml7yvd954tstdaaae"},{"key":"method","value":"reset"}]}]}] } -#[ignore] #[tokio::test] async fn test_store_contract() { // Sender publickey {"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Aggx3Gp4SJOHzZK4WDen/j5EXutf78JB87DQA5/7Z59y"} @@ -275,7 +273,6 @@ async fn test_store_contract() { // code_id = 547 } -#[ignore] #[tokio::test] async fn test_instantiate_contract() { let mnemonic = "coyote electric million purchase tennis skin quiz inside helmet call glimpse pulse turkey hint maze iron festival run bomb regular legend prepare service angry".to_string();