From e5f14b0d434556c7193c0d8a38659d1e3b0bad7d Mon Sep 17 00:00:00 2001 From: AntonAndell Date: Thu, 2 Nov 2023 15:10:52 +0100 Subject: [PATCH] add native withdraws to assetManager for archway --- .../cw-asset-manager/Cargo.toml | 2 + .../cw-asset-manager/src/contract.rs | 176 +++++++++++++++++- .../cw-asset-manager/src/external.rs | 54 ++++++ .../cw-asset-manager/src/helpers.rs | 25 +++ .../cw-asset-manager/src/lib.rs | 1 + .../cw-asset-manager/src/state.rs | 3 + contracts/cw-common/src/asset_manager_msg.rs | 8 + contracts/cw-common/src/xcall_data_types.rs | 18 ++ 8 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 contracts/core-contracts/cw-asset-manager/src/external.rs diff --git a/contracts/core-contracts/cw-asset-manager/Cargo.toml b/contracts/core-contracts/cw-asset-manager/Cargo.toml index 62c3a3b..6f5ddeb 100644 --- a/contracts/core-contracts/cw-asset-manager/Cargo.toml +++ b/contracts/core-contracts/cw-asset-manager/Cargo.toml @@ -20,6 +20,8 @@ crate-type = ["cdylib", "rlib"] backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all instantiate/execute/query exports library = [] +default = ["archway"] +archway = [] [package.metadata.scripts] optimize = """docker run --rm -v "$(pwd)":/code \ diff --git a/contracts/core-contracts/cw-asset-manager/src/contract.rs b/contracts/core-contracts/cw-asset-manager/src/contract.rs index ca2fee2..8512db5 100644 --- a/contracts/core-contracts/cw-asset-manager/src/contract.rs +++ b/contracts/core-contracts/cw-asset-manager/src/contract.rs @@ -8,7 +8,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw20::{AllowanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; -use cw_common::asset_manager_msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cw_common::asset_manager_msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use cw_common::network_address::IconAddressValidation; use cw_common::network_address::NetworkAddress; use cw_common::x_call_msg::{GetNetworkAddress, XCallMsg}; @@ -57,7 +57,14 @@ pub fn execute( ExecuteMsg::HandleCallMessage { from, data } => { exec::handle_xcall_msg(deps, env, info, from, data) } - + ExecuteMsg::ConfigureNative { + native_token_address, + native_token_manager, + } => { + let owner = OWNER.load(deps.storage).map_err(ContractError::Std)?; + ensure_eq!(owner, info.sender, ContractError::OnlyOwner); + exec::setup_native_token(deps, native_token_address, native_token_manager) + } ExecuteMsg::Deposit { token_address, amount, @@ -160,6 +167,25 @@ mod exec { Ok(Response::default()) } + pub fn setup_native_token( + deps: DepsMut, + native_token_address: String, + native_token_manager: String, + ) -> Result { + let token_addr = deps + .api + .addr_validate(&native_token_address) + .map_err(ContractError::Std)?; + let token_manager_addr = deps + .api + .addr_validate(&native_token_manager) + .map_err(ContractError::Std)?; + NATIVE_TOKEN_ADDRESS.save(deps.storage, &token_addr)?; + NATIVE_TOKEN_MANAGER.save(deps.storage, &token_manager_addr)?; + + Ok(Response::default()) + } + #[allow(clippy::too_many_arguments)] pub fn deposit_cw20_tokens( deps: DepsMut, @@ -307,6 +333,19 @@ mod exec { transfer_tokens(deps, account, token_address, amount)? } + + DecodedStruct::WithdrawNativeTo(data_struct) => { + let icon_am = ICON_ASSET_MANAGER.load(deps.storage)?; + if from != icon_am.to_string() { + return Err(ContractError::OnlyIconAssetManager {}); + } + + let token_address = data_struct.token_address; + let account = data_struct.user_address; + let amount = Uint128::from(data_struct.amount); + + swap_to_native(deps, account, token_address, amount)? + } }; Ok(res) @@ -341,6 +380,68 @@ mod exec { }; Ok(Response::new().add_submessage(sub_msg)) } + + #[cfg(feature = "archway")] + fn swap_to_native( + deps: DepsMut, + account: String, + token_address: String, + amount: Uint128, + ) -> Result { + use crate::external::{ConfigResponse, Cw20HookMsg, StakingQueryMsg}; + + deps.api.addr_validate(&account)?; + deps.api.addr_validate(&token_address)?; + let query_msg = &StakingQueryMsg::ConfigInfo {}; + let manager = NATIVE_TOKEN_MANAGER.load(deps.storage)?; + let query_resp: ConfigResponse = deps + .querier + .query_wasm_smart::(manager.clone(), &query_msg)?; + let swap_contract = query_resp.swap_contract_addr; + + let hook = &Cw20HookMsg::Swap { + belief_price: None, + max_spread: None, + to: Some(account.clone()), + }; + let transfer_msg = &Cw20ExecuteMsg::Send { + contract: swap_contract.clone(), + amount, + msg: to_binary(hook)?, + }; + + let execute_msg: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: token_address.clone(), + msg: to_binary(transfer_msg)?, + funds: vec![], + }); + + let sub_msg = SubMsg { + id: SUCCESS_REPLY_MSG, + msg: execute_msg, + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Never, + }; + Ok(Response::new().add_submessage(sub_msg)) + } + + #[cfg(not(any(feature = "archway")))] + fn swap_to_native( + deps: DepsMut, + account: String, + token_address: String, + amount: Uint128, + ) -> Result { + transfer_tokens(deps, account, token_address, amount) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .map_err(ContractError::Std)?; + + Ok(Response::default().add_attribute("migrate", "successful")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -640,6 +741,77 @@ mod tests { assert!(result.is_err()); } + #[cfg(feature = "archway")] + #[test] + fn test_withdraw_native_archway() { + use cw_common::xcall_data_types::WithdrawNativeTo; + + use crate::external::ConfigResponse; + + let (mut deps, env, info, _) = test_setup(); + let mocked_xcall_info = mock_info("xcall", &[]); + + let staking = "staking"; + let swap = "swap"; + let token = "token1"; + let account = "account1"; + + deps.querier.update_wasm(|r: &WasmQuery| match r { + WasmQuery::Smart { + contract_addr: _, + msg: _, + } => { + SystemResult::Ok(ContractResult::Ok( + to_binary(&ConfigResponse{ + admin: "".to_string(), + pause_admin: "".to_string(), + bond_denom: "".to_string(), + liquid_token_addr: "".to_string(), + swap_contract_addr: swap.to_string(), + treasury_contract_addr: "".to_string(), + team_wallet_addr: "".to_string(), + commission_percentage: 1, + team_percentage: 1, + liquidity_percentage: 1, + delegations: vec![], + contract_state: false, + }).unwrap(), + )) + } + _ => todo!(), + }); + + let resp = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ConfigureNative { + native_token_address: token.to_string(), + native_token_manager: staking.to_string() + }, + ); + assert!(resp.is_ok()); + + let am_nw = "0x01.icon/cxc2d01de5013778d71d99f985e4e2ff3a9b48a66c"; + let withdraw_msg = WithdrawNativeTo { + token_address: token.to_string(), + amount: 1000, + user_address: account.to_string(), + }; + + let exe_msg = ExecuteMsg::HandleCallMessage { + from: am_nw.to_string(), + data: withdraw_msg.rlp_bytes().to_vec(), + }; + let resp = execute( + deps.as_mut(), + env.clone(), + mocked_xcall_info.clone(), + exe_msg, + ); + assert!(resp.is_ok()); + } + #[test] fn test_configure_network() { //verify configuration updates from owner side diff --git a/contracts/core-contracts/cw-asset-manager/src/external.rs b/contracts/core-contracts/cw-asset-manager/src/external.rs new file mode 100644 index 0000000..47c2bc3 --- /dev/null +++ b/contracts/core-contracts/cw-asset-manager/src/external.rs @@ -0,0 +1,54 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[cw_serde] +pub enum StakingQueryMsg { + ConfigInfo {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct ConfigResponse { + /// Should be multi-sig, is able to update the config + pub admin: String, + /// Should be single-sig, is able to pause the contract + pub pause_admin: String, + /// This is the denomination we can stake (and only one we accept for payments) + pub bond_denom: String, + /// Liquid token address + pub liquid_token_addr: String, + /// Swap contract address + pub swap_contract_addr: String, + /// Liquid Treasury contract address + pub treasury_contract_addr: String, + /// Team wallet address + pub team_wallet_addr: String, + /// percentage of commission taken off of staking rewards + pub commission_percentage: u16, + /// percentage of rewards for the team + pub team_percentage: u16, + /// percentage of rewards for the liquidity providers + pub liquidity_percentage: u16, + /// Delegations preferences for a whitelist of validators, each validator has a delegation percentage + pub delegations: Vec, + /// contract state (active/paused) + pub contract_state: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct DelegationPercentage { + pub validator: String, + pub percentage: u16, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Cw20HookMsg { + /// Sell a given amount of asset + Swap { + belief_price: Option, + max_spread: Option, + to: Option, + }, +} diff --git a/contracts/core-contracts/cw-asset-manager/src/helpers.rs b/contracts/core-contracts/cw-asset-manager/src/helpers.rs index 04d00bd..fea7fa9 100644 --- a/contracts/core-contracts/cw-asset-manager/src/helpers.rs +++ b/contracts/core-contracts/cw-asset-manager/src/helpers.rs @@ -8,6 +8,7 @@ use crate::error::ContractError; #[derive(Debug)] pub enum DecodedStruct { WithdrawTo(WithdrawTo), + WithdrawNativeTo(WithdrawTo), DepositRevert(DepositRevert), } @@ -45,6 +46,30 @@ pub fn decode_encoded_bytes(data: &[u8]) -> Result<(&str, DecodedStruct), Contra Ok(("WithdrawTo", DecodedStruct::WithdrawTo(withdraw_to))) } + "WithdrawNativeTo" => { + if rlp.item_count()? != 4 { + return Err(DecoderError::RlpInvalidLength.into()); + } + + // Extract the fields + let token: String = rlp.val_at(1)?; + let user_address: String = rlp.val_at(2)?; + let amount: u128 = rlp.val_at(3)?; + + // Create a new WithdrawTo instance + let withdraw_to = WithdrawTo { + token_address: token, + user_address, + amount, + }; + + // Return the decoded struct as an OK variant + Ok(( + "WithdrawNativeTo", + DecodedStruct::WithdrawNativeTo(withdraw_to), + )) + } + "DepositRevert" => { if rlp.item_count()? != 4 { return Err(DecoderError::RlpInvalidLength.into()); diff --git a/contracts/core-contracts/cw-asset-manager/src/lib.rs b/contracts/core-contracts/cw-asset-manager/src/lib.rs index 39e6d15..fa201cd 100644 --- a/contracts/core-contracts/cw-asset-manager/src/lib.rs +++ b/contracts/core-contracts/cw-asset-manager/src/lib.rs @@ -1,6 +1,7 @@ pub mod constants; pub mod contract; mod error; +pub mod external; pub mod helpers; pub mod state; pub use crate::error::ContractError; diff --git a/contracts/core-contracts/cw-asset-manager/src/state.rs b/contracts/core-contracts/cw-asset-manager/src/state.rs index c369f24..14c0f92 100644 --- a/contracts/core-contracts/cw-asset-manager/src/state.rs +++ b/contracts/core-contracts/cw-asset-manager/src/state.rs @@ -12,3 +12,6 @@ pub const NID: Item = Item::new("network_id"); pub const ICON_ASSET_MANAGER: Item = Item::new("icon_asset_manager_network_address"); pub const ICON_NET_ID: Item = Item::new("icon_asset_manager_network_id"); + +pub const NATIVE_TOKEN_ADDRESS: Item = Item::new("native_token_address"); +pub const NATIVE_TOKEN_MANAGER: Item = Item::new("native_token_manager"); \ No newline at end of file diff --git a/contracts/cw-common/src/asset_manager_msg.rs b/contracts/cw-common/src/asset_manager_msg.rs index 93fcdd9..8468d07 100644 --- a/contracts/cw-common/src/asset_manager_msg.rs +++ b/contracts/cw-common/src/asset_manager_msg.rs @@ -25,6 +25,11 @@ pub enum ExecuteMsg { destination_asset_manager: String, }, + ConfigureNative { + native_token_address: String, + native_token_manager: String, + }, + HandleCallMessage { from: String, data: Vec, @@ -58,3 +63,6 @@ pub struct NetIdResponse { pub x_call_nid: String, //NetID pub icon_nid: String, //NetID } + +#[cw_serde] +pub struct MigrateMsg {} diff --git a/contracts/cw-common/src/xcall_data_types.rs b/contracts/cw-common/src/xcall_data_types.rs index 67004ea..6502276 100644 --- a/contracts/cw-common/src/xcall_data_types.rs +++ b/contracts/cw-common/src/xcall_data_types.rs @@ -27,6 +27,13 @@ pub struct WithdrawTo { pub amount: u128, } +#[cw_serde] +pub struct WithdrawNativeTo { + pub token_address: String, + pub user_address: String, + pub amount: u128, +} + //for testing impl Encodable for Deposit { //specify the encoding logic for struct's fields so that rlp_bytes() can alo use @@ -65,6 +72,17 @@ impl Encodable for WithdrawTo { } } +impl Encodable for WithdrawNativeTo { + fn rlp_append(&self, s: &mut RlpStream) { + let method = "WithdrawNativeTo".to_string(); + s.begin_list(4) + .append(&method) + .append(&self.token_address) + .append(&self.user_address) + .append(&self.amount); + } +} + #[cfg(test)] mod tests { use super::*;