Skip to content

Commit

Permalink
feat: add support for denominates assets in asset manager
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonAndell committed Dec 28, 2023
1 parent 7a9a212 commit 5fdb4e9
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 15 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions contracts/core-contracts/cw-asset-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ serde = { version = "1.0.163", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.40" }
cw-common = { path = "../../cw-common" }
cw20 = "1.0.1"
cw-denom = "2.2.0"
cw_ibc_rlp_lib = {git = "https://github.com/icon-project/IBC-Integration.git", branch="main", package = "common"}
cw-utils = "1.0.1"
debug_print = {workspace=true}
Expand Down
188 changes: 173 additions & 15 deletions contracts/core-contracts/cw-asset-manager/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,49 @@ pub fn execute(
ensure_eq!(owner, info.sender, ContractError::OnlyOwner);
exec::setup_native_token(deps, native_token_address, native_token_manager)
}
ExecuteMsg::DepositDenom {
denom,
to,
data
} => {
ensure!(info.funds.len() == 2, ContractError::InvalidFunds);
let token = info.funds.iter().find(|c| c.denom == denom).unwrap();
ensure!(!token.amount.is_zero(), ContractError::InvalidAmount);

// Will be checked by xCall if the user has paid correct fees
let native = info.funds.iter().find(|c| c.denom != denom).unwrap();

let nid = NID.load(deps.storage)?;
let depositor = NetworkAddress::new(nid.as_str(), info.sender.as_str());
ensure!(cw_denom::validate_native_denom(denom.clone()).is_ok(), ContractError::InvalidFunds);
let addr = deps
.api
.addr_validate(&denom);
ensure!(addr.is_err() || !is_contract(deps.querier, &addr.unwrap()), ContractError::InvalidFunds);
// To avoid confusion restrict usage of denoms that start with the networkId
ensure!(!denom.clone().starts_with((nid.to_string() +"/").as_str()), ContractError::InvalidFunds);

let recipient: NetworkAddress = match to {
Some(to_address) => {
let nw_addr = NetworkAddress::from_str(&to_address).unwrap();
if !nw_addr.validate_foreign_addresses() {
return Err(ContractError::InvalidRecipientAddress);
}
nw_addr

Check warning on line 113 in contracts/core-contracts/cw-asset-manager/src/contract.rs

View check run for this annotation

Codecov / codecov/patch

contracts/core-contracts/cw-asset-manager/src/contract.rs#L108-L113

Added lines #L108 - L113 were not covered by tests
}
// if `to` is not provided, sender address is used as recipient
None => depositor,
};
let data = data.unwrap_or_default();
exec::deposit_tokens(deps,
denom,
info.sender.clone(),
token.amount,
recipient,
data,
vec![],
vec![native.clone()])
}
ExecuteMsg::Deposit {
token_address,
amount,
Expand Down Expand Up @@ -121,7 +164,7 @@ pub fn execute(
amount,
recipient,
data,
info,
info.funds,
)?;
Ok(res)
}
Expand All @@ -131,7 +174,7 @@ pub fn execute(
mod exec {
use std::str::FromStr;

use cosmwasm_std::CosmosMsg;
use cosmwasm_std::{CosmosMsg, Coin, BankMsg};
use cw_ibc_rlp_lib::rlp::Encodable;

use cw_common::{helpers::query_network_address, xcall_data_types::DepositRevert};
Expand Down Expand Up @@ -208,9 +251,8 @@ mod exec {
amount: Uint128,
to: NetworkAddress,
data: Vec<u8>,
info: MessageInfo,
funds: Vec<Coin>,
) -> Result<Response, ContractError> {
let dest_am = ICON_ASSET_MANAGER.load(deps.storage)?;

let contract_address = &env.contract.address;

Expand Down Expand Up @@ -242,12 +284,24 @@ mod exec {
});

//transfer sub msg
let transfer_sub_msg = SubMsg {
id: SUCCESS_REPLY_MSG,
msg: execute_msg,
gas_limit: None,
reply_on: cosmwasm_std::ReplyOn::Never,
};
let transfer_sub_msg = SubMsg::new(execute_msg);

deposit_tokens(deps, token_address, from, amount, to, data, vec![transfer_sub_msg], funds)

}

#[allow(clippy::too_many_arguments)]
pub fn deposit_tokens(
deps: DepsMut,
token_address: String,
from: Addr,
amount: Uint128,
to: NetworkAddress,
data: Vec<u8>,
msgs: Vec<SubMsg>,
funds: Vec<Coin>,
) -> Result<Response, ContractError> {
let dest_am = ICON_ASSET_MANAGER.load(deps.storage)?;

//create xcall rlp encode data
let xcall_data = Deposit {
Expand Down Expand Up @@ -280,7 +334,7 @@ mod exec {
let xcall_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: source_xcall.to_string(),
msg: to_binary(&xcall_message)?,
funds: info.funds,
funds: funds,
});

let xcall_sub_msg = SubMsg {
Expand All @@ -299,7 +353,8 @@ mod exec {
let event = Event::new("Deposit").add_attributes(attributes);

let resp = Response::new()
.add_submessages(vec![transfer_sub_msg, xcall_sub_msg])
.add_submessages(msgs)
.add_submessage(xcall_sub_msg)
.add_event(event);

Ok(resp)
Expand Down Expand Up @@ -372,7 +427,20 @@ mod exec {
amount: Uint128,
) -> Result<Response, ContractError> {
deps.api.addr_validate(&account)?;
deps.api.addr_validate(&token_address)?;
let addr = deps
.api
.addr_validate(&token_address);

if addr.is_err() || !is_contract(deps.querier, &addr.unwrap()) {
let coin = Coin{
denom: token_address,
amount: amount,
};
let msg = BankMsg::Send { to_address: account, amount: vec![coin] };
let sub_msg = SubMsg::new(msg);

return Ok(Response::new().add_submessage(sub_msg));
}

let transfer_msg = &Cw20ExecuteMsg::Transfer {
recipient: account,
Expand Down Expand Up @@ -503,7 +571,7 @@ mod tests {
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
ContractInfoResponse, ContractResult, MemoryStorage, OwnedDeps, SystemResult, Uint128,
WasmQuery,
WasmQuery, Coin, SystemError,
};
use cw_common::xcall_manager_msg::QueryMsg::GetProtocols;
use cw_ibc_rlp_lib::rlp::Encodable;
Expand Down Expand Up @@ -555,7 +623,10 @@ mod tests {
SystemResult::Ok(ContractResult::Ok(to_binary(&allowance_resp).unwrap()))
}
}
WasmQuery::ContractInfo { contract_addr: _ } => {
WasmQuery::ContractInfo { contract_addr: addr } => {
if addr.starts_with("denom") {
return SystemResult::Err(SystemError::NoSuchContract{addr: addr.to_string()});
}
let mut response = ContractInfoResponse::default();
response.code_id = 1;
response.creator = "sender".to_string();
Expand Down Expand Up @@ -697,6 +768,44 @@ mod tests {
execute(deps.as_mut(), env, info, msg).unwrap();
}

#[test]
fn test_deposit_denom() {
let (mut deps, env, _, _) = test_setup();
let denom = "denom/ibc-ics-20/test";

// Test Deposit message (checking expected field value)
let msg = ExecuteMsg::DepositDenom {
denom: denom.to_string(),
to: None,
data: None,
};
let funds = Coin{denom:denom.to_string(), amount:Uint128::new(100)};
let fee = Coin{denom:"arch".to_string(), amount:Uint128::new(100)};
let info = mock_info("user", &[funds, fee]);

let response = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
// Verify the response contains the expected sub-messages
assert_eq!(response.messages.len(), 1);

// Verify the event attributes
if let Some(event) = response.events.get(0) {
assert_eq!(event.ty, "Deposit");
assert_eq!(event.attributes.len(), 3);

// Verify the individual event attributes
for attribute in &event.attributes {
match attribute.key.as_str() {
"Token" => assert_eq!(attribute.value, denom),
"To" => assert_eq!(attribute.value, "0x44.archway/user"),
"Amount" => assert_eq!(attribute.value, "100"),
_ => panic!("Unexpected attribute key"),

Check warning on line 801 in contracts/core-contracts/cw-asset-manager/src/contract.rs

View check run for this annotation

Codecov / codecov/patch

contracts/core-contracts/cw-asset-manager/src/contract.rs#L801

Added line #L801 was not covered by tests
}
}
} else {
panic!("No event found in the response");

Check warning on line 805 in contracts/core-contracts/cw-asset-manager/src/contract.rs

View check run for this annotation

Codecov / codecov/patch

contracts/core-contracts/cw-asset-manager/src/contract.rs#L805

Added line #L805 was not covered by tests
}
}

#[test]
fn test_handle_xcall() {
let (mut deps, env, _, _) = test_setup();
Expand Down Expand Up @@ -769,6 +878,55 @@ mod tests {
assert!(result.is_err());
}

#[test]
fn test_withdraw_denom() {
let (mut deps, env, _, _) = test_setup();
let mocked_xcall_info = mock_info("xcall", &[]);

let xcall_nw = "0x44.archway/xcall";
let token = "denom/ibc-ics-20/token";
let account = "account1";
//create deposit revert(expected) xcall msg deps
let x_deposit_revert = DepositRevert {
token_address: token.to_string(),
account: account.to_string(),
amount: 100,
};

//create valid handle_call_message
let msg = ExecuteMsg::HandleCallMessage {
from: xcall_nw.to_string(),
data: x_deposit_revert.rlp_bytes().to_vec(),
protocols: vec![],
};

let result = execute(deps.as_mut(), env.clone(), mocked_xcall_info.clone(), msg);
//check for valid xcall expected msg data
assert!(result.is_ok());

//for withdrawTo
let am_nw = "0x01.icon/cxc2d01de5013778d71d99f985e4e2ff3a9b48a66c";
let withdraw_msg = WithdrawTo {
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(),
protocols: vec![],
};
let resp = execute(
deps.as_mut(),
env.clone(),
mocked_xcall_info.clone(),
exe_msg,
);
assert!(resp.is_ok());
}


#[cfg(feature = "archway")]
#[test]
fn test_withdraw_native_archway() {
Expand Down
3 changes: 3 additions & 0 deletions contracts/core-contracts/cw-asset-manager/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub enum ContractError {

#[error("Unauthorized")]
Unauthorized,

#[error("Invalid funds")]
InvalidFunds,
}

impl From<DecoderError> for ContractError {
Expand Down
8 changes: 8 additions & 0 deletions contracts/cw-common/src/asset_manager_msg.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::string;

use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{Addr, Uint128};

Expand All @@ -18,6 +20,12 @@ pub enum ExecuteMsg {
data: Option<Vec<u8>>,
},

DepositDenom{
denom: String,
to: Option<String>,
data: Option<Vec<u8>>,
},

//TODO: introduce deposit transfer,
// to field: network address(validation) to receive the (can be loans, another user address) (optional) defaults to caller
// data field: depending upon the to address (optional)
Expand Down

0 comments on commit 5fdb4e9

Please sign in to comment.