diff --git a/Cargo.lock b/Cargo.lock index 37aa837..25a2d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -588,9 +597,10 @@ dependencies = [ [[package]] name = "cw-ics20-latest" -version = "1.0.8" +version = "1.0.9" dependencies = [ "anybuf", + "bs58", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-testing-util", @@ -601,6 +611,7 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-ics20-msg 0.0.3", + "hex", "oraiswap 0.2.0 (git+https://github.com/oraichain/oraiswap.git?rev=f4b0cc2f96e87938c6f203ac0a82012bcfe4b17b)", "sha256", "skip", diff --git a/contracts/cw-ics20-latest/Cargo.toml b/contracts/cw-ics20-latest/Cargo.toml index 97cb5d1..33b2bd0 100644 --- a/contracts/cw-ics20-latest/Cargo.toml +++ b/contracts/cw-ics20-latest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw-ics20-latest" -version = "1.0.8" +version = "1.0.9" authors = ["Ethan Frey , Oraichain Labs"] edition = "2021" description = "IBC Enabled contracts that receives CW20 tokens and sends them over ICS20 to a remote chain" @@ -32,7 +32,8 @@ sha256 = "=1.1.0" skip = { workspace = true } tokenfactory = { workspace = true } token-bindings = { workspace = true } - +bs58 = "0.5.1" +hex = "0.4.3" [dev-dependencies] cosmwasm-vm = { workspace = true } # osmosis-test-tube = { workspace = true } diff --git a/contracts/cw-ics20-latest/src/contract.rs b/contracts/cw-ics20-latest/src/contract.rs index 8f225c1..d74983a 100644 --- a/contracts/cw-ics20-latest/src/contract.rs +++ b/contracts/cw-ics20-latest/src/contract.rs @@ -9,6 +9,7 @@ use cw2::set_contract_version; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use cw20_ics20_msg::converter::ConverterController; use cw20_ics20_msg::helper::parse_ibc_wasm_port_id; +use cw_controllers::AdminError; use cw_storage_plus::Bound; use oraiswap::asset::AssetInfo; use oraiswap::router::RouterController; @@ -165,7 +166,7 @@ pub fn execute( orai_receiver, args, } => ibc_hooks_receive(deps, env, info, func, orai_receiver, args), - ExecuteMsg::RegisterDenom(msg) => register_denom(deps, info, msg), + ExecuteMsg::RegisterDenom(msg) => register_denom(deps, env, info, msg), ExecuteMsg::WithdrawAsset { coin, receiver } => { execute_withdraw_asset(deps, info, coin, receiver) } @@ -201,10 +202,13 @@ fn execute_withdraw_asset( pub fn register_denom( deps: DepsMut, + env: Env, info: MessageInfo, msg: RegisterDenomMsg, ) -> Result { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; + if !ADMIN.is_admin(deps.as_ref(), &info.sender)? && info.sender != env.contract.address { + return Err(ContractError::Admin(AdminError::NotAdmin {})); + }; let config = CONFIG.load(deps.storage)?; @@ -840,7 +844,7 @@ pub fn execute_delete_mapping_pair( } #[entry_point] -pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { // we don't need to save anything if migrating from the same version set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; diff --git a/contracts/cw-ics20-latest/src/ibc.rs b/contracts/cw-ics20-latest/src/ibc.rs index 4da58fa..2c5a65b 100644 --- a/contracts/cw-ics20-latest/src/ibc.rs +++ b/contracts/cw-ics20-latest/src/ibc.rs @@ -2,32 +2,33 @@ use std::ops::Mul; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - attr, entry_point, from_json, to_json_binary, wasm_execute, Api, Binary, CosmosMsg, Decimal, - Deps, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannel, IbcChannelCloseMsg, - IbcChannelConnectMsg, IbcChannelOpenMsg, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket, - IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, - Order, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, SubMsgResult, - Uint128, + attr, entry_point, from_json, to_json_binary, wasm_execute, Api, Binary, Coin, CosmosMsg, + Decimal, Deps, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannel, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcEndpoint, IbcMsg, IbcOrder, + IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, + IbcTimeout, Order, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, + SubMsgResult, Uint128, }; use cw20_ics20_msg::helper::{ - denom_to_asset_info, get_prefix_decode_bech32, parse_asset_info_denom, + denom_to_asset_info, get_full_denom, get_prefix_decode_bech32, parse_asset_info_denom, }; use cw_storage_plus::Map; use oraiswap::asset::AssetInfo; use oraiswap::router::{RouterController, SwapOperation}; use skip::entry_point::ExecuteMsg as EntryPointExecuteMsg; +use token_bindings::Metadata; use crate::contract::build_mint_mapping_msg; use crate::error::{ContractError, Never}; -use crate::msg::ExecuteMsg; +use crate::msg::{ExecuteMsg, RegisterDenomMsg}; use crate::state::{ get_key_ics20_ibc_denom, ics20_denoms, undo_reduce_channel_balance, ALLOW_LIST, CHANNEL_INFO, CONFIG, RELAYER_FEE, TOKEN_FEE, }; use cw20_ics20_msg::amount::{convert_remote_to_local, Amount}; use cw20_ics20_msg::msg::FeeData; -use cw20_ics20_msg::state::{ChannelInfo, Ratio}; +use cw20_ics20_msg::state::{ChannelInfo, MappingMetadata, Ratio}; pub const ICS20_VERSION: &str = "ics20-1"; pub const ICS20_ORDERING: IbcOrder = IbcOrder::Unordered; @@ -329,7 +330,7 @@ fn handle_ibc_packet_receive_native_remote_chain( let config = CONFIG.load(storage)?; let mut cosmos_msgs: Vec = vec![]; let ibc_packet_amount = msg.amount.to_string(); - let attributes = vec![ + let attributes: Vec<(&str, &str)> = vec![ ("action", "receive_native"), ("sender", &msg.sender), ("receiver", &msg.receiver), @@ -341,9 +342,47 @@ fn handle_ibc_packet_receive_native_remote_chain( // key in form transfer/channel-0/foo let ibc_denom = get_key_ics20_ibc_denom(&packet.dest.port_id, &packet.dest.channel_id, denom); - let pair_mapping = ics20_denoms() - .load(storage, &ibc_denom) - .map_err(|_| ContractError::NotOnMappingList {})?; + let pair_mapping = match ics20_denoms().load(storage, &ibc_denom) { + Ok(pair_mapping) => pair_mapping, + Err(_) => { + let (prefix, denom) = + denom + .split_once("0x") + .ok_or(ContractError::Std(StdError::GenericErr { + msg: String::from("Cannot parse denom"), + }))?; + + let bytes_address = hex::decode(denom).map_err(|_| { + ContractError::Std(StdError::GenericErr { + msg: String::from("Invalid hex address"), + }) + })?; + let base58_address = bs58::encode(bytes_address).into_string(); + let base58_denom = format!("{}0x{}", prefix, base58_address); + // push a register denom msg to the contract + cosmos_msgs.push( + wasm_execute( + env.contract.address.to_string(), + &ExecuteMsg::RegisterDenom(RegisterDenomMsg { + subdenom: base58_denom.clone(), + metadata: None, + }), + vec![Coin::new(1, "orai")], + )? + .into(), + ); + let new_metadata = MappingMetadata { + asset_info: AssetInfo::NativeToken { + denom: get_full_denom(config.token_factory_addr.to_string(), base58_denom), + }, + remote_decimals: 1, // Since we don't know metadata of remote_chain token, we set it to 1 in both decimals + asset_info_decimals: 1, + is_mint_burn: true, // Always mint burn if we don't know the metadata + }; + ics20_denoms().save(storage, &ibc_denom, &new_metadata)?; + new_metadata + } + }; let initial_receive_asset_info = pair_mapping.asset_info; let to_send = Amount::from_parts( parse_asset_info_denom(&initial_receive_asset_info), diff --git a/contracts/cw-ics20-latest/src/testing/ibc_tests.rs b/contracts/cw-ics20-latest/src/testing/ibc_tests.rs index 040fb22..3a2d405 100644 --- a/contracts/cw-ics20-latest/src/testing/ibc_tests.rs +++ b/contracts/cw-ics20-latest/src/testing/ibc_tests.rs @@ -7,9 +7,11 @@ use cosmwasm_std::{ use cosmwasm_testing_util::mock::MockContract; use cosmwasm_vm::testing::MockInstanceOptions; use cw20_ics20_msg::converter::ConverterController; +use cw20_ics20_msg::helper::get_full_denom; use cw_controllers::AdminError; use oraiswap::asset::AssetInfo; use oraiswap::router::RouterController; +use token_bindings::Metadata; use crate::ibc::{ convert_remote_denom_to_evm_prefix, deduct_fee, deduct_relayer_fee, deduct_token_fee, @@ -28,8 +30,8 @@ use cosmwasm_std::{ use crate::error::ContractError; use crate::state::{ - get_key_ics20_ibc_denom, increase_channel_balance, reduce_channel_balance, Config, ADMIN, - CHANNEL_REVERSE_STATE, CONFIG, RELAYER_FEE, REPLY_ARGS, TOKEN_FEE, + get_key_ics20_ibc_denom, ics20_denoms, increase_channel_balance, reduce_channel_balance, + Config, ADMIN, CHANNEL_REVERSE_STATE, CONFIG, RELAYER_FEE, REPLY_ARGS, TOKEN_FEE, }; use cw20::{Cw20CoinVerified, Cw20ExecuteMsg, Cw20ReceiveMsg}; use cw20_ics20_msg::amount::{convert_remote_to_local, Amount}; @@ -41,7 +43,7 @@ use crate::contract::{ }; use crate::msg::{ AllowMsg, ChannelResponse, ConfigResponse, ExecuteMsg, InitMsg, ListChannelsResponse, - ListMappingResponse, PairQuery, QueryMsg, + ListMappingResponse, PairQuery, QueryMsg, RegisterDenomMsg, }; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, to_json_vec}; @@ -230,28 +232,70 @@ fn send_native_from_remote_mapping_not_found() { let send_channel = "channel-9"; let cw20_addr = "token-addr"; let custom_addr = "custom-addr"; - let cw20_denom = "cw20:token-addr"; + let cw20_denom = "oraib0x10407cEa4B614AB11bd05B326193d84ec20851f6"; let gas_limit = 1234567; let mut deps = setup( &["channel-1", "channel-7", send_channel], &[(cw20_addr, gas_limit)], ); - + let config = CONFIG.load(deps.as_ref().storage).unwrap(); // prepare some mock packets let recv_packet = mock_receive_packet_remote_to_local(send_channel, 876543210, cw20_denom, custom_addr, None); + let (prefix, denom) = cw20_denom.split_once("0x").unwrap(); + let bytes_address = hex::decode(denom) + .map_err(|_| { + ContractError::Std(StdError::GenericErr { + msg: String::from("Invalid hex address"), + }) + }) + .unwrap(); + let base58_address = bs58::encode(bytes_address).into_string(); + let base58_denom = format!("{}0x{}", prefix, base58_address); // we can receive this denom, channel balance should increase let msg = IbcPacketReceiveMsg::new(recv_packet.clone(), relayer); let res = ibc_packet_receive(deps.as_mut(), mock_env(), msg).unwrap(); - // assert_eq!(res, StdError) + assert_eq!( - res.attributes - .into_iter() - .find(|attr| attr.key.eq("error")) - .unwrap() - .value, - "You can only send native tokens that has a map to the corresponding asset info" + res.messages[0].msg, + wasm_execute( + "cosmos2contract", + &ExecuteMsg::RegisterDenom(RegisterDenomMsg { + subdenom: String::from(base58_denom.clone()), + metadata: None + }), + vec![Coin::new(1u128.into(), "orai")] + ) + .unwrap() + .into() + ); + execute( + deps.as_mut(), + mock_env(), + mock_info("cosmos2contract", &[]), + ExecuteMsg::RegisterDenom(RegisterDenomMsg { + subdenom: String::from(denom), + metadata: None, + }), + ) + .unwrap(); + let pair_mapping = ics20_denoms() + .load( + deps.as_ref().storage, + "wasm.cosmos2contract/channel-9/oraib0x10407cEa4B614AB11bd05B326193d84ec20851f6", + ) + .unwrap(); + assert_eq!( + pair_mapping, + MappingMetadata { + asset_info: AssetInfo::NativeToken { + denom: get_full_denom(config.token_factory_addr.to_string(), base58_denom), + }, + remote_decimals: 1, + asset_info_decimals: 1, + is_mint_burn: true + } ); } diff --git a/packages/cw20-ics20-msg/src/helper.rs b/packages/cw20-ics20-msg/src/helper.rs index a07cda9..acae607 100644 --- a/packages/cw20-ics20-msg/src/helper.rs +++ b/packages/cw20-ics20-msg/src/helper.rs @@ -49,6 +49,10 @@ pub fn to_orai_bridge_address(address: &str) -> StdResult { }) } +pub fn get_full_denom(factory_contract: String, subdenom: String) -> String { + format!("factory/{}/{}", factory_contract, subdenom) +} + #[cfg(test)] mod tests { use crate::helper::{get_prefix_decode_bech32, to_orai_bridge_address};