Skip to content

Commit

Permalink
Merge pull request #57 from balancednetwork/feature/add-denom-token-t…
Browse files Browse the repository at this point in the history
…o-asset-manager

feat: Add support for denominated assets in asset manager
  • Loading branch information
AntonAndell authored Jan 18, 2024
2 parents 6c5aa45 + 5453a5b commit 4d109b4
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 17 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
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 @@ 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

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 @@ pub fn execute(
amount,
recipient,
data,
info,
info.funds,
)?;
Ok(res)
}
Expand All @@ -131,7 +179,7 @@ pub fn execute(
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 @@ 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;

let query_msg = &Cw20QueryMsg::Allowance {
Expand Down Expand Up @@ -242,12 +288,32 @@ 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 +346,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,
});

let xcall_sub_msg = SubMsg {
Expand All @@ -299,7 +365,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 +439,21 @@ 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,
};
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 query {
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 @@ 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 +785,50 @@ 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 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 @@ 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: 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

0 comments on commit 4d109b4

Please sign in to comment.