Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for denominated assets in asset manager #57

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
214 changes: 197 additions & 17 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,54 @@
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();

ibrizsabin marked this conversation as resolved.
Show resolved Hide resolved
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

Check warning on line 97 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#L97

Added line #L97 was not covered by tests
);
let addr = deps.api.addr_validate(&denom);
ensure!(

Check warning on line 100 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#L100

Added line #L100 was not covered by tests
addr.is_err() || !is_contract(deps.querier, &addr.unwrap()),
ContractError::InvalidFunds

Check warning on line 102 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#L102

Added line #L102 was not covered by tests
);
// To avoid confusion restrict usage of denoms that start with the networkId
ensure!(

Check warning on line 105 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#L105

Added line #L105 was not covered by tests
!denom.clone().starts_with((nid.to_string() + "/").as_str()),
ContractError::InvalidFunds

Check warning on line 107 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#L107

Added line #L107 was not covered by tests
);

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 116 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#L111-L116

Added lines #L111 - L116 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 +169,7 @@
amount,
recipient,
data,
info,
info.funds,
)?;
Ok(res)
}
Expand All @@ -131,7 +179,7 @@
mod exec {
use std::str::FromStr;

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

use cw_common::{helpers::query_network_address, xcall_data_types::DepositRevert};
Expand Down Expand Up @@ -208,10 +256,8 @@
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;

let query_msg = &Cw20QueryMsg::Allowance {
Expand Down Expand Up @@ -242,12 +288,32 @@
});

//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 +346,7 @@
let xcall_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: source_xcall.to_string(),
msg: to_binary(&xcall_message)?,
funds: info.funds,
funds,
});

let xcall_sub_msg = SubMsg {
Expand All @@ -299,7 +365,8 @@
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 +439,21 @@
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 {
ibrizsabin marked this conversation as resolved.
Show resolved Hide resolved
denom: token_address,
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 @@ -502,8 +583,8 @@
mod tests {
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier},
ContractInfoResponse, ContractResult, MemoryStorage, OwnedDeps, SystemResult, Uint128,
WasmQuery,
Coin, ContractInfoResponse, ContractResult, MemoryStorage, OwnedDeps, SystemError,
SystemResult, Uint128, WasmQuery,
};
use cw_common::xcall_manager_msg::QueryMsg::GetProtocols;
use cw_ibc_rlp_lib::rlp::Encodable;
Expand Down Expand Up @@ -555,7 +636,14 @@
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 +785,50 @@
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 824 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#L824

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

Check warning on line 828 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#L828

Added line #L828 was not covered by tests
}
}

#[test]
fn test_handle_xcall() {
let (mut deps, env, _, _) = test_setup();
Expand Down Expand Up @@ -769,6 +901,54 @@
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: None,
};

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: None,
};
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
6 changes: 6 additions & 0 deletions contracts/cw-common/src/asset_manager_msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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