From 8aefe518dfb76407c6cc988b5774edcf0db8fb45 Mon Sep 17 00:00:00 2001 From: "Alien hunter no. 42" <84127070+alienHunterOnMars@users.noreply.github.com> Date: Tue, 23 Nov 2021 16:40:20 +0530 Subject: [PATCH 1/3] adding STT proxy (in progress) --- Cargo.lock | 16 ++ contracts/proxy_to_stt/.cargo/config | 5 + contracts/proxy_to_stt/Cargo.toml | 34 +++ contracts/proxy_to_stt/README.md | 122 ++++++++++ contracts/proxy_to_stt/examples/schema.rs | 21 ++ .../proxy_to_stt/schema/cw20_hook_msg.json | 18 ++ .../proxy_to_stt/schema/execute_msg.json | 136 +++++++++++ .../proxy_to_stt/schema/instantiate_msg.json | 29 +++ .../proxy_to_stt/schema/migrate_msg.json | 5 + contracts/proxy_to_stt/schema/query_msg.json | 54 +++++ contracts/proxy_to_stt/src/contract.rs | 218 ++++++++++++++++++ contracts/proxy_to_stt/src/error.rs | 14 ++ contracts/proxy_to_stt/src/lib.rs | 3 + contracts/proxy_to_stt/src/state.rs | 16 ++ packages/astroport_generator_proxy/src/lib.rs | 1 + .../src/stt_staking.rs | 187 +++++++++++++++ 16 files changed, 879 insertions(+) create mode 100644 contracts/proxy_to_stt/.cargo/config create mode 100644 contracts/proxy_to_stt/Cargo.toml create mode 100644 contracts/proxy_to_stt/README.md create mode 100644 contracts/proxy_to_stt/examples/schema.rs create mode 100644 contracts/proxy_to_stt/schema/cw20_hook_msg.json create mode 100644 contracts/proxy_to_stt/schema/execute_msg.json create mode 100644 contracts/proxy_to_stt/schema/instantiate_msg.json create mode 100644 contracts/proxy_to_stt/schema/migrate_msg.json create mode 100644 contracts/proxy_to_stt/schema/query_msg.json create mode 100644 contracts/proxy_to_stt/src/contract.rs create mode 100644 contracts/proxy_to_stt/src/error.rs create mode 100644 contracts/proxy_to_stt/src/lib.rs create mode 100644 contracts/proxy_to_stt/src/state.rs create mode 100644 packages/astroport_generator_proxy/src/stt_staking.rs diff --git a/Cargo.lock b/Cargo.lock index 8789544..80e226f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "generator-proxy-to-stt" +version = "0.0.0" +dependencies = [ + "astroport-generator-proxy", + "cosmwasm-bignumber", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "cw20", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "generator-proxy-to-vkr" version = "0.0.0" diff --git a/contracts/proxy_to_stt/.cargo/config b/contracts/proxy_to_stt/.cargo/config new file mode 100644 index 0000000..7d1a066 --- /dev/null +++ b/contracts/proxy_to_stt/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/proxy_to_stt/Cargo.toml b/contracts/proxy_to_stt/Cargo.toml new file mode 100644 index 0000000..5af5b16 --- /dev/null +++ b/contracts/proxy_to_stt/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "generator-proxy-to-stt" +version = "0.0.0" +authors = ["_astromartian"] +edition = "2018" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +cosmwasm-std = { version = "0.16.0" } +cosmwasm-bignumber = "2.2.0" +cw-storage-plus = "0.8.0" +schemars = "0.8.1" +serde = { version = "1.0.125", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.24" } +cw2 = "0.8.0" +cw20 = "0.8.0" +astroport-generator-proxy = { path = "../../packages/astroport_generator_proxy", default-features = false, version = "1.0.0"} + +[dev-dependencies] +cosmwasm-schema = { version = "0.16.0" } diff --git a/contracts/proxy_to_stt/README.md b/contracts/proxy_to_stt/README.md new file mode 100644 index 0000000..8091fab --- /dev/null +++ b/contracts/proxy_to_stt/README.md @@ -0,0 +1,122 @@ +# Generator Proxy to STT LP Staking Rewards + +The generator proxy contract interacts with the STT LP staking contract (the Astroport dual rewards feature). + +StarTerra LP Staking contract implementation: https://github.com/starterra/app-smart-contracts/blob/develop-col5/contracts/staking/src/contract.rs + +The staking via proxy guide is [here](https://miro.medium.com/max/1400/0*8hn2NSnZJZTa9YGV). + +--- + +## InstantiateMsg + +Inits with required contract addresses for depositing and reward distribution. + +```json +{ + "generator_contract_addr": "terra...", + "pair_addr": "terra...", + "lp_token_addr": "terra...", + "reward_contract_addr": "terra...", + "reward_token_addr": "terra..." +} +``` + +## ExecuteMsg + +### `receive` + +CW20 receive msg. + +```json +{ + "receive": { + "sender": "terra...", + "amount": "123", + "msg": "" + } +} +``` + +### `update_rewards` + +Updates token proxy rewards. + +```json +{ + "update_rewards": {} +} +``` + +### `send_rewards` + +Sends token rewards amount for given address. + +```json +{ + "send_rewards": { + "account": "terra...", + "amount": "123" + } +} +``` + +### `withdraw` + +Withdraws token rewards amount for given address. + +```json +{ + "withdraw": { + "account": "terra...", + "amount": "123" + } +} +``` + +### `emergency_withdraw` + +Withdraws token rewards amount for given address. + +```json +{ + "emergency_withdraw": { + "account": "terra...", + "amount": "123" + } +} +``` + +## QueryMsg + +All query messages are described below. A custom struct is defined for each query response. + +### `deposit` + +Returns deposited/staked token amount. + +```json +{ + "deposit": {} +} +``` + +### `reward` + +Gives token proxy reward amount. + +```json +{ + "reward": {} +} +``` + +### `pending_token` + +Gives token proxy reward pending amount. + +```json +{ + "pending_token": {} +} +``` diff --git a/contracts/proxy_to_stt/examples/schema.rs b/contracts/proxy_to_stt/examples/schema.rs new file mode 100644 index 0000000..a178e97 --- /dev/null +++ b/contracts/proxy_to_stt/examples/schema.rs @@ -0,0 +1,21 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use astroport_generator_proxy::generator_proxy::{ + Cw20HookMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(Cw20HookMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); +} diff --git a/contracts/proxy_to_stt/schema/cw20_hook_msg.json b/contracts/proxy_to_stt/schema/cw20_hook_msg.json new file mode 100644 index 0000000..8c71a4f --- /dev/null +++ b/contracts/proxy_to_stt/schema/cw20_hook_msg.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20HookMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/proxy_to_stt/schema/execute_msg.json b/contracts/proxy_to_stt/schema/execute_msg.json new file mode 100644 index 0000000..f43dd6c --- /dev/null +++ b/contracts/proxy_to_stt/schema/execute_msg.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_rewards" + ], + "properties": { + "update_rewards": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "send_rewards" + ], + "properties": { + "send_rewards": { + "type": "object", + "required": [ + "account", + "amount" + ], + "properties": { + "account": { + "$ref": "#/definitions/Addr" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "required": [ + "account", + "amount" + ], + "properties": { + "account": { + "$ref": "#/definitions/Addr" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "emergency_withdraw" + ], + "properties": { + "emergency_withdraw": { + "type": "object", + "required": [ + "account", + "amount" + ], + "properties": { + "account": { + "$ref": "#/definitions/Addr" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/proxy_to_stt/schema/instantiate_msg.json b/contracts/proxy_to_stt/schema/instantiate_msg.json new file mode 100644 index 0000000..4905b64 --- /dev/null +++ b/contracts/proxy_to_stt/schema/instantiate_msg.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "generator_contract_addr", + "lp_token_addr", + "pair_addr", + "reward_contract_addr", + "reward_token_addr" + ], + "properties": { + "generator_contract_addr": { + "type": "string" + }, + "lp_token_addr": { + "type": "string" + }, + "pair_addr": { + "type": "string" + }, + "reward_contract_addr": { + "type": "string" + }, + "reward_token_addr": { + "type": "string" + } + } +} diff --git a/contracts/proxy_to_stt/schema/migrate_msg.json b/contracts/proxy_to_stt/schema/migrate_msg.json new file mode 100644 index 0000000..87b18ea --- /dev/null +++ b/contracts/proxy_to_stt/schema/migrate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/proxy_to_stt/schema/query_msg.json b/contracts/proxy_to_stt/schema/query_msg.json new file mode 100644 index 0000000..7259093 --- /dev/null +++ b/contracts/proxy_to_stt/schema/query_msg.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "deposit" + ], + "properties": { + "deposit": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward" + ], + "properties": { + "reward": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_token" + ], + "properties": { + "pending_token": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_info" + ], + "properties": { + "reward_info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/proxy_to_stt/src/contract.rs b/contracts/proxy_to_stt/src/contract.rs new file mode 100644 index 0000000..7f3a7a9 --- /dev/null +++ b/contracts/proxy_to_stt/src/contract.rs @@ -0,0 +1,218 @@ +use cosmwasm_std::{ + entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, SubMsg, Uint128, WasmMsg, +}; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; + +use crate::error::ContractError; +use crate::state::{Config, CONFIG}; +use astroport_generator_proxy::generator_proxy::{ + Cw20HookMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; +use astroport_generator_proxy::stt_staking::{ + Cw20HookMsg as AncCw20HookMsg, ExecuteMsg as AncExecuteMsg, QueryMsg as AncQueryMsg, + StakerInfoResponse, +}; +use cw2::set_contract_version; + +// version info for migration info +const CONTRACT_NAME: &str = "astroport-generator-proxy-to-stt"; +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)?; + + let config = Config { + generator_contract_addr: deps.api.addr_validate(&msg.generator_contract_addr)?, + pair_addr: deps.api.addr_validate(&msg.pair_addr)?, + lp_token_addr: deps.api.addr_validate(&msg.lp_token_addr)?, + reward_contract_addr: deps.api.addr_validate(&msg.reward_contract_addr)?, + reward_token_addr: deps.api.addr_validate(&msg.reward_token_addr)?, + }; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::UpdateRewards {} => update_rewards(deps), + ExecuteMsg::SendRewards { account, amount } => send_rewards(deps, info, account, amount), + ExecuteMsg::Withdraw { account, amount } => withdraw(deps, info, account, amount), + ExecuteMsg::EmergencyWithdraw { account, amount } => withdraw(deps, info, account, amount), + } +} + +/// @dev Receives LP tokens sent by Generator contract. +/// Stakes them with the Starterra LP Staking contract +fn receive_cw20( + deps: DepsMut, + _env: Env, + info: MessageInfo, + cw20_msg: Cw20ReceiveMsg, +) -> Result { + let mut response = Response::new(); + let cfg = CONFIG.load(deps.storage)?; + + if let Ok(Cw20HookMsg::Deposit {}) = from_binary(&cw20_msg.msg) { + if cw20_msg.sender != cfg.generator_contract_addr || info.sender != cfg.lp_token_addr { + return Err(ContractError::Unauthorized {}); + } + response + .messages + .push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cfg.lp_token_addr.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: cfg.reward_contract_addr.to_string(), + amount: cw20_msg.amount, + msg: to_binary(&AncCw20HookMsg::Bond {})?, + })?, + }))); + } else { + return Err(ContractError::IncorrectCw20HookMessageVariant {}); + } + Ok(response) +} + +/// @dev Claims pending rewards from the STT LP staking contract +fn update_rewards(deps: DepsMut) -> Result { + let mut response = Response::new(); + let cfg = CONFIG.load(deps.storage)?; + + response + .messages + .push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cfg.reward_contract_addr.to_string(), + funds: vec![], + msg: to_binary(&AncExecuteMsg::Withdraw {})?, + }))); + + Ok(response) +} + +/// @dev Transfers STT rewards +/// @param account : User to which STT tokens are to be transferred +/// @param amount : Number of STT to be transferred +fn send_rewards( + deps: DepsMut, + info: MessageInfo, + account: Addr, + amount: Uint128, +) -> Result { + let mut response = Response::new(); + let cfg = CONFIG.load(deps.storage)?; + if info.sender != cfg.generator_contract_addr { + return Err(ContractError::Unauthorized {}); + }; + + response + .messages + .push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cfg.reward_token_addr.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: account.to_string(), + amount, + })?, + funds: vec![], + }))); + Ok(response) +} + +/// @dev Withdraws LP Tokens from the staking contract. Rewards are NOT claimed when withdrawing LP tokens +/// @param account : User to which LP tokens are to be transferred +/// @param amount : Number of LP to be unstaked and transferred +fn withdraw( + deps: DepsMut, + info: MessageInfo, + account: Addr, + amount: Uint128, +) -> Result { + let mut response = Response::new(); + let cfg = CONFIG.load(deps.storage)?; + if info.sender != cfg.generator_contract_addr { + return Err(ContractError::Unauthorized {}); + }; + + // withdraw from the end reward contract + response.messages.push(SubMsg::new(WasmMsg::Execute { + contract_addr: cfg.reward_contract_addr.to_string(), + funds: vec![], + msg: to_binary(&AncExecuteMsg::Unbond { + amount: amount.into(), + })?, + })); + + response.messages.push(SubMsg::new(WasmMsg::Execute { + contract_addr: cfg.lp_token_addr.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: account.to_string(), + amount, + })?, + })); + + Ok(response) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let cfg = CONFIG.load(deps.storage)?; + match msg { + QueryMsg::Deposit {} => { + let res: StakerInfoResponse = deps.querier.query_wasm_smart( + cfg.reward_contract_addr, + &AncQueryMsg::StakerInfo { + staker: env.contract.address.to_string(), + block_height: None, + }, + )?; + let deposit_amount = res.bond_amount; + to_binary(&deposit_amount) + } + QueryMsg::Reward {} => { + let res: Result = deps.querier.query_wasm_smart( + cfg.reward_token_addr, + &Cw20QueryMsg::Balance { + address: env.contract.address.into_string(), + }, + ); + let reward_amount = res?.balance; + + to_binary(&reward_amount) + } + QueryMsg::PendingToken {} => { + let res: StakerInfoResponse = deps.querier.query_wasm_smart( + cfg.reward_contract_addr, + &AncQueryMsg::StakerInfo { + staker: env.contract.address.to_string(), + block_height: None, + }, + )?; + let pending_reward = res.pending_reward; + to_binary(&Some(pending_reward)) + } + QueryMsg::RewardInfo {} => { + let config = CONFIG.load(deps.storage)?; + to_binary(&config.reward_token_addr) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + Ok(Response::default()) +} diff --git a/contracts/proxy_to_stt/src/error.rs b/contracts/proxy_to_stt/src/error.rs new file mode 100644 index 0000000..240e4b5 --- /dev/null +++ b/contracts/proxy_to_stt/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("Incorrect CW20 hook message variant!")] + IncorrectCw20HookMessageVariant {}, +} diff --git a/contracts/proxy_to_stt/src/lib.rs b/contracts/proxy_to_stt/src/lib.rs new file mode 100644 index 0000000..3d3e89c --- /dev/null +++ b/contracts/proxy_to_stt/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod error; +pub mod state; diff --git a/contracts/proxy_to_stt/src/state.rs b/contracts/proxy_to_stt/src/state.rs new file mode 100644 index 0000000..4373496 --- /dev/null +++ b/contracts/proxy_to_stt/src/state.rs @@ -0,0 +1,16 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Config { + pub generator_contract_addr: Addr, + pub pair_addr: Addr, + pub lp_token_addr: Addr, + pub reward_contract_addr: Addr, + pub reward_token_addr: Addr, +} + +pub const CONFIG: Item = Item::new("config"); diff --git a/packages/astroport_generator_proxy/src/lib.rs b/packages/astroport_generator_proxy/src/lib.rs index b67082e..0a281d9 100644 --- a/packages/astroport_generator_proxy/src/lib.rs +++ b/packages/astroport_generator_proxy/src/lib.rs @@ -7,6 +7,7 @@ pub mod mars_staking; pub mod mine_staking; pub mod orion_staking; pub mod psi_staking; +pub mod stt_staking; pub mod whale_staking; #[allow(clippy::all)] diff --git a/packages/astroport_generator_proxy/src/stt_staking.rs b/packages/astroport_generator_proxy/src/stt_staking.rs new file mode 100644 index 0000000..0eb597b --- /dev/null +++ b/packages/astroport_generator_proxy/src/stt_staking.rs @@ -0,0 +1,187 @@ +use cosmwasm_std::{Decimal, Order, Uint128}; +use cw20::Cw20ReceiveMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum OrderBy { + Asc, + Desc, +} + +impl Into for OrderBy { + fn into(self) -> Order { + if self == OrderBy::Asc { + Order::Ascending + } else { + Order::Descending + } + } +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + pub owner: String, + pub starterra_token: String, + pub staking_token: String, // lp token of STT-UST pair contract + pub burn_address: String, + pub gateway_address: String, + pub distribution_schedule: Vec, + pub unbond_config: Vec, + pub faction_name: String, + pub fee_configuration: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Receive(Cw20ReceiveMsg), + SubmitToUnbond { + amount: Uint128, + }, + Unbond { + amount: Uint128, + }, + MoveBond { + destination_contract: String, + }, + /// Withdraw pending rewards + Withdraw {}, + BurningWithdraw { + amount: Uint128, + }, + UpdateConfig { + owner: Option, + burn_address: Option, + gateway_address: Option, + paused: Option, + distribution_schedule: Option>, + fee_configuration: Option>, + unbond_config: Option>, + }, + EmergencyWithdraw { + amount: Uint128, + to: String, + }, + AcceptOwnership {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Cw20HookMsg { + Bond {}, +} + +/// We currently take no arguments for migrations +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MigrateMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Config {}, + State { + block_time: Option, + }, + StakerInfo { + staker: String, + block_time: Option, + }, + StakersInfo { + start_after: Option, + limit: Option, + order_by: Option, + block_time: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StakerInfo { + pub reward_index: Decimal, + pub bond_amount: Uint128, + pub pending_reward: Uint128, +} + +// We define a custom struct for config response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct ConfigResponse { + pub owner: String, + pub starterra_token: String, + pub staking_token: String, + pub burn_address: String, + pub gateway_address: String, + pub distribution_schedule: Vec, + pub faction_name: String, + pub paused: bool, + pub max_pending_unbond_count: u64, + pub fee_configuration: Vec, + pub unbond_config: Vec, +} + +// We define a custom struct for state response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StateResponse { + pub last_distributed: u64, + pub total_bond_amount: Uint128, + pub global_reward_index: Decimal, +} + +// We define a custom struct for staker info response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StakerInfoResponse { + pub staker: String, + pub reward_index: Decimal, + pub bond_amount: Uint128, + pub pending_reward: Uint128, + pub rewards_per_fee: Vec, + pub time_to_best_fee: Option, + pub pending_unbond_left: Option, +} + +// We define a custom struct for reward config +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct RewardConfig { + pub percent_lost: u64, + pub amount: Uint128, +} + +// We define a custom struct for stakers info response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StakersInfoResponse { + pub stakers: Vec, +} + +// We define a custom struct for unbond config +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UnbondConfig { + pub minimum_time: u64, + pub percentage_loss: u64, +} + +// We define a custom struct for unbond info +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UnbondInfo { + pub submission_time: u64, + pub amount: Uint128, +} + +// We define a custom struct for unbond info response +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)] +pub struct UnbondInfoResponse { + pub submitted_to_unbond: Vec, + pub sum: Uint128, +} + +// We define a custom struct for distribution schedule entities +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct DistributionScheduleRecord { + pub start_time: u64, + pub end_time: u64, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct OperationFee { + pub operation: String, + pub fee: Uint128, +} From d08019e0c5b9dc7b82083cb6d4c186d236632c11 Mon Sep 17 00:00:00 2001 From: "Alien hunter no. 42" <84127070+alienHunterOnMars@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:07:53 +0530 Subject: [PATCH 2/3] added STT staking contract --- contracts/proxy_to_stt/src/contract.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/contracts/proxy_to_stt/src/contract.rs b/contracts/proxy_to_stt/src/contract.rs index 7f3a7a9..116b05b 100644 --- a/contracts/proxy_to_stt/src/contract.rs +++ b/contracts/proxy_to_stt/src/contract.rs @@ -10,7 +10,7 @@ use astroport_generator_proxy::generator_proxy::{ Cw20HookMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, }; use astroport_generator_proxy::stt_staking::{ - Cw20HookMsg as AncCw20HookMsg, ExecuteMsg as AncExecuteMsg, QueryMsg as AncQueryMsg, + Cw20HookMsg as SttCw20HookMsg, ExecuteMsg as SttExecuteMsg, QueryMsg as SttQueryMsg, StakerInfoResponse, }; use cw2::set_contract_version; @@ -79,7 +79,7 @@ fn receive_cw20( msg: to_binary(&Cw20ExecuteMsg::Send { contract: cfg.reward_contract_addr.to_string(), amount: cw20_msg.amount, - msg: to_binary(&AncCw20HookMsg::Bond {})?, + msg: to_binary(&SttCw20HookMsg::Bond {})?, })?, }))); } else { @@ -98,7 +98,7 @@ fn update_rewards(deps: DepsMut) -> Result { .push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cfg.reward_contract_addr.to_string(), funds: vec![], - msg: to_binary(&AncExecuteMsg::Withdraw {})?, + msg: to_binary(&SttExecuteMsg::Withdraw {})?, }))); Ok(response) @@ -147,11 +147,18 @@ fn withdraw( return Err(ContractError::Unauthorized {}); }; - // withdraw from the end reward contract response.messages.push(SubMsg::new(WasmMsg::Execute { contract_addr: cfg.reward_contract_addr.to_string(), funds: vec![], - msg: to_binary(&AncExecuteMsg::Unbond { + msg: to_binary(&SttExecuteMsg::SubmitToUnbond { + amount: amount.into(), + })?, + })); + + response.messages.push(SubMsg::new(WasmMsg::Execute { + contract_addr: cfg.reward_contract_addr.to_string(), + funds: vec![], + msg: to_binary(&SttExecuteMsg::Unbond { amount: amount.into(), })?, })); @@ -175,9 +182,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Deposit {} => { let res: StakerInfoResponse = deps.querier.query_wasm_smart( cfg.reward_contract_addr, - &AncQueryMsg::StakerInfo { + &SttQueryMsg::StakerInfo { staker: env.contract.address.to_string(), - block_height: None, + block_time: None, }, )?; let deposit_amount = res.bond_amount; @@ -197,9 +204,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::PendingToken {} => { let res: StakerInfoResponse = deps.querier.query_wasm_smart( cfg.reward_contract_addr, - &AncQueryMsg::StakerInfo { + &SttQueryMsg::StakerInfo { staker: env.contract.address.to_string(), - block_height: None, + block_time: None, }, )?; let pending_reward = res.pending_reward; From 8099fe8e99712c5e273b016acc28a37bf970d43d Mon Sep 17 00:00:00 2001 From: "Alien hunter no. 42" <84127070+alienHunterOnMars@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:14:01 +0530 Subject: [PATCH 3/3] added tests --- .../proxy_to_stt/src/testing/mock_querier.rs | 175 +++++++++ contracts/proxy_to_stt/src/testing/mod.rs | 2 + contracts/proxy_to_stt/src/testing/tests.rs | 333 ++++++++++++++++++ 3 files changed, 510 insertions(+) create mode 100644 contracts/proxy_to_stt/src/testing/mock_querier.rs create mode 100644 contracts/proxy_to_stt/src/testing/mod.rs create mode 100644 contracts/proxy_to_stt/src/testing/tests.rs diff --git a/contracts/proxy_to_stt/src/testing/mock_querier.rs b/contracts/proxy_to_stt/src/testing/mock_querier.rs new file mode 100644 index 0000000..42b158d --- /dev/null +++ b/contracts/proxy_to_stt/src/testing/mock_querier.rs @@ -0,0 +1,175 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use astroport_generator_proxy::stt_staking::StakerInfoResponse; +use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{ + from_binary, from_slice, to_binary, Coin, ContractResult, Decimal, Empty, OwnedDeps, Querier, + QuerierResult, QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, +}; +use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg}; + +/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies +/// this uses our CustomQuerier. +pub fn mock_dependencies( + contract_balance: &[Coin], +) -> OwnedDeps { + let custom_querier: WasmMockQuerier = + WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); + + OwnedDeps { + api: MockApi::default(), + storage: MockStorage::default(), + querier: custom_querier, + } +} + +pub struct WasmMockQuerier { + base: MockQuerier, + token_querier: TokenQuerier, + reward_querier: RewardQuerier, +} + +#[derive(Clone, Default)] +pub struct TokenQuerier { + // this lets us iterate over all pairs that match the first string + balances: HashMap>, +} + +impl TokenQuerier { + pub fn new(balances: &[(&String, &[(&String, &Uint128)])]) -> Self { + TokenQuerier { + balances: balances_to_map(balances), + } + } +} + +pub(crate) fn balances_to_map( + balances: &[(&String, &[(&String, &Uint128)])], +) -> HashMap> { + let mut balances_map: HashMap> = HashMap::new(); + for (contract_addr, balances) in balances.iter() { + let mut contract_balances_map: HashMap = HashMap::new(); + for (addr, balance) in balances.iter() { + contract_balances_map.insert(addr.to_string(), **balance); + } + + balances_map.insert(contract_addr.to_string(), contract_balances_map); + } + balances_map +} + +#[derive(Clone, Default)] +pub struct RewardQuerier { + pending_reward: Uint128, + deposit_amount: Uint128, +} + +impl RewardQuerier { + pub fn new(pending_reward: Uint128, deposit_amount: Uint128) -> Self { + RewardQuerier { + pending_reward, + deposit_amount, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + StakerInfo { + staker: String, + block_height: Option, + }, +} + +impl Querier for WasmMockQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + // MockQuerier doesn't support Custom, so we ignore it completely here + let request: QueryRequest = match from_slice(bin_request) { + Ok(v) => v, + Err(e) => { + return SystemResult::Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }) + } + }; + self.handle_query(&request) + } +} + +impl WasmMockQuerier { + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match &request { + QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => match from_binary(msg) { + Ok(QueryMsg::StakerInfo { + staker: _, + block_height: _, + }) => SystemResult::Ok(ContractResult::from(to_binary(&StakerInfoResponse { + staker: "generator0000".to_string(), + reward_index: Decimal::zero(), + bond_amount: self.reward_querier.deposit_amount, + pending_reward: self.reward_querier.pending_reward, + rewards_per_fee: vec![], + time_to_best_fee: None, + pending_unbond_left: None, + }))), + _ => match from_binary(msg).unwrap() { + Cw20QueryMsg::Balance { address } => { + let balances: &HashMap = + match self.token_querier.balances.get(contract_addr) { + Some(balances) => balances, + None => { + return SystemResult::Err(SystemError::InvalidRequest { + error: format!( + "No balance info exists for the contract {}", + contract_addr + ), + request: msg.as_slice().into(), + }) + } + }; + + let balance = match balances.get(&address) { + Some(v) => *v, + None => { + return SystemResult::Ok(ContractResult::Ok( + to_binary(&Cw20BalanceResponse { + balance: Uint128::zero(), + }) + .unwrap(), + )); + } + }; + + SystemResult::Ok(ContractResult::Ok( + to_binary(&Cw20BalanceResponse { balance }).unwrap(), + )) + } + _ => panic!("Query Not Mocked"), + }, + }, + _ => self.base.handle_query(request), + } + } +} + +impl WasmMockQuerier { + pub fn new(base: MockQuerier) -> Self { + WasmMockQuerier { + base, + token_querier: TokenQuerier::default(), + reward_querier: RewardQuerier::default(), + } + } + + pub fn with_token_balances(&mut self, balances: &[(&String, &[(&String, &Uint128)])]) { + self.token_querier = TokenQuerier::new(balances); + } + + pub fn with_reward_info(&mut self, pending_reward: Uint128, deposit_amount: Uint128) { + self.reward_querier = RewardQuerier::new(pending_reward, deposit_amount); + } +} diff --git a/contracts/proxy_to_stt/src/testing/mod.rs b/contracts/proxy_to_stt/src/testing/mod.rs new file mode 100644 index 0000000..a1e507b --- /dev/null +++ b/contracts/proxy_to_stt/src/testing/mod.rs @@ -0,0 +1,2 @@ +mod mock_querier; +mod tests; diff --git a/contracts/proxy_to_stt/src/testing/tests.rs b/contracts/proxy_to_stt/src/testing/tests.rs new file mode 100644 index 0000000..6b3f26c --- /dev/null +++ b/contracts/proxy_to_stt/src/testing/tests.rs @@ -0,0 +1,333 @@ +use crate::contract::{execute, instantiate, query}; +use crate::error::ContractError; +use crate::state::{Config, CONFIG}; +use crate::testing::mock_querier::mock_dependencies; +use astroport_generator_proxy::anc_staking::{ + Cw20HookMsg as AncCw20HookMsg, ExecuteMsg as AncExecuteMsg, +}; +use astroport_generator_proxy::generator_proxy::{ + Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg, +}; +use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; +use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, SubMsg, Uint128, WasmMsg}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; + +#[test] +fn test_proper_initialization() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let config: Config = CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!("generator0000", config.generator_contract_addr.as_str()); + assert_eq!("pair0000", config.pair_addr.as_str()); + assert_eq!("ancust0000", config.lp_token_addr.as_str()); + assert_eq!("reward0000", config.reward_contract_addr.as_str()); + assert_eq!("anc0000", config.reward_token_addr.as_str()); +} + +#[test] +fn test_deposit() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // deposit fails when not sent by LP token + let deposit_msg = ExecuteMsg::Receive(Cw20ReceiveMsg { + sender: "generator0000".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&Cw20HookMsg::Deposit {}).unwrap(), + }); + + let res = execute(deps.as_mut(), mock_env(), info, deposit_msg).unwrap_err(); + match res { + ContractError::Unauthorized {} => {} + _ => panic!("Must return unauthorized error"), + }; + + // deposit fails when cw20 sender is not generator + let info = mock_info("ancust0000", &[]); + let deposit_msg = ExecuteMsg::Receive(Cw20ReceiveMsg { + sender: "addr0000".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&Cw20HookMsg::Deposit {}).unwrap(), + }); + + let res = execute(deps.as_mut(), mock_env(), info, deposit_msg).unwrap_err(); + match res { + ContractError::Unauthorized {} => {} + _ => panic!("Must return unauthorized error"), + }; + + // successfull deposit + let info = mock_info("ancust0000", &[]); + let deposit_msg = ExecuteMsg::Receive(Cw20ReceiveMsg { + sender: "generator0000".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&Cw20HookMsg::Deposit {}).unwrap(), + }); + let res = execute(deps.as_mut(), mock_env(), info, deposit_msg).unwrap(); + + assert_eq!( + res.messages, + vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "ancust0000".to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: "reward0000".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&AncCw20HookMsg::Bond {}).unwrap(), + }) + .unwrap(), + }))] + ); + + deps.querier + .with_reward_info(Uint128::from(5u128), Uint128::from(100u128)); + let res = query(deps.as_ref(), mock_env(), QueryMsg::Deposit {}).unwrap(); + let query_res: Uint128 = from_binary(&res).unwrap(); + assert_eq!(query_res, Uint128::from(100u128)); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::PendingToken {}).unwrap(); + let query_res: Uint128 = from_binary(&res).unwrap(); + assert_eq!(query_res, Uint128::from(5u128)); +} + +#[test] +fn test_update_rewards() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // claim rewards from ANC staking contract + let claim_rewards_msg = ExecuteMsg::UpdateRewards {}; + let res = execute(deps.as_mut(), mock_env(), info, claim_rewards_msg).unwrap(); + + assert_eq!( + res.messages, + vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "reward0000".to_string(), + funds: vec![], + msg: to_binary(&AncExecuteMsg::Withdraw {}).unwrap(), + }))] + ); + + deps.querier.with_token_balances(&[( + &"anc0000".to_string(), + &[(&MOCK_CONTRACT_ADDR.to_string(), &Uint128::from(5u128))], + )]); + deps.querier + .with_reward_info(Uint128::from(0u128), Uint128::from(100u128)); + + // token balance on contract increases from claim + let res = query(deps.as_ref(), mock_env(), QueryMsg::Reward {}).unwrap(); + let query_res: Uint128 = from_binary(&res).unwrap(); + assert_eq!(query_res, Uint128::from(5u128)); + + // no pending tokens + let res = query(deps.as_ref(), mock_env(), QueryMsg::PendingToken {}).unwrap(); + let query_res: Uint128 = from_binary(&res).unwrap(); + assert_eq!(query_res, Uint128::from(0u128)); +} + +#[test] +fn test_send_rewards() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // transfer reward token to user + // fails when called from unauthorized + let send_rewards_msg = ExecuteMsg::SendRewards { + account: Addr::unchecked("addr0000"), + amount: Uint128::new(100), + }; + let res = execute(deps.as_mut(), mock_env(), info, send_rewards_msg.clone()).unwrap_err(); + match res { + ContractError::Unauthorized {} => {} + _ => panic!("Must return unauthorized error"), + }; + + // succeeds when coming from generator + let generator_info = mock_info("generator0000", &[]); + let res = execute(deps.as_mut(), mock_env(), generator_info, send_rewards_msg).unwrap(); + + assert_eq!( + res.messages, + vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "anc0000".to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: "addr0000".to_string(), + amount: Uint128::new(100), + }) + .unwrap(), + funds: vec![], + }))] + ); +} + +#[test] +fn test_withdraw() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // unbond and send lp tokens to user + // fails when called from unauthorized + let withrdaw_msg = ExecuteMsg::Withdraw { + account: Addr::unchecked("addr0000"), + amount: Uint128::new(100), + }; + let res = execute(deps.as_mut(), mock_env(), info, withrdaw_msg.clone()).unwrap_err(); + match res { + ContractError::Unauthorized {} => {} + _ => panic!("Must return unauthorized error"), + }; + + // succeeds when coming from generator + let generator_info = mock_info("generator0000", &[]); + let res = execute(deps.as_mut(), mock_env(), generator_info, withrdaw_msg).unwrap(); + + assert_eq!( + res.messages, + vec![ + SubMsg::new(WasmMsg::Execute { + contract_addr: "reward0000".to_string(), + funds: vec![], + msg: to_binary(&AncExecuteMsg::Unbond { + amount: Uint128::new(100), + }) + .unwrap(), + }), + SubMsg::new(WasmMsg::Execute { + contract_addr: "ancust0000".to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: "addr0000".to_string(), + amount: Uint128::new(100), + }) + .unwrap(), + }) + ] + ); +} + +#[test] +fn test_emergency_withdraw() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); + + // unbond and send lp tokens to user + // fails when called from unauthorized + let withrdaw_msg = ExecuteMsg::EmergencyWithdraw { + account: Addr::unchecked("addr0000"), + amount: Uint128::new(100), + }; + let res = execute(deps.as_mut(), mock_env(), info, withrdaw_msg.clone()).unwrap_err(); + match res { + ContractError::Unauthorized {} => {} + _ => panic!("Must return unauthorized error"), + }; + + // succeeds when coming from generator + let generator_info = mock_info("generator0000", &[]); + let res = execute(deps.as_mut(), mock_env(), generator_info, withrdaw_msg).unwrap(); + + assert_eq!( + res.messages, + vec![ + SubMsg::new(WasmMsg::Execute { + contract_addr: "reward0000".to_string(), + funds: vec![], + msg: to_binary(&AncExecuteMsg::Unbond { + amount: Uint128::new(100), + }) + .unwrap(), + }), + SubMsg::new(WasmMsg::Execute { + contract_addr: "ancust0000".to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: "addr0000".to_string(), + amount: Uint128::new(100), + }) + .unwrap(), + }) + ] + ); +} + +#[test] +fn test_query_reward_info() { + let mut deps = mock_dependencies(&[]); + + let msg = InstantiateMsg { + generator_contract_addr: "generator0000".to_string(), + pair_addr: "pair0000".to_string(), + lp_token_addr: "ancust0000".to_string(), + reward_contract_addr: "reward0000".to_string(), + reward_token_addr: "anc0000".to_string(), + }; + + let info = mock_info("addr0000", &[]); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let res = query(deps.as_ref(), mock_env(), QueryMsg::RewardInfo {}).unwrap(); + let query_res: Addr = from_binary(&res).unwrap(); + assert_eq!(query_res, Addr::unchecked("anc0000")); +}