-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add centralized connection cosmwasm (#233)
* feat: cosmwasm cetralized adapter added * feat: add claim fees * fix: contract balance taken from env * style: cargo formatted code * feat: reply and migrate entry points added * feat: msg type updated * fix: parameter fix * fix: review fixes
- Loading branch information
Showing
11 changed files
with
911 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
45 changes: 45 additions & 0 deletions
45
contracts/cosmwasm-vm/cw-centralized-connection/Cargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
[package] | ||
name = "cw-centralized-connection" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
exclude = [ | ||
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. | ||
"contract.wasm", | ||
"hash.txt", | ||
] | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[features] | ||
# for more explicit tests, cargo test --features=backtraces | ||
backtraces = ["cosmwasm-std/backtraces"] | ||
library = [] | ||
|
||
[package.metadata.scripts] | ||
optimize = """docker run --rm -v "$(pwd)":/code \ | ||
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ | ||
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ | ||
cosmwasm/rust-optimizer:0.12.10 | ||
""" | ||
|
||
[dependencies] | ||
cosmwasm-schema = {workspace=true} | ||
cosmwasm-std = { workspace=true} | ||
cw-storage-plus = {workspace=true} | ||
cw2 = {workspace=true} | ||
schemars = {workspace=true} | ||
serde = { workspace=true} | ||
thiserror = { workspace=true} | ||
common ={ workspace=true} | ||
cw-xcall-lib = { path="../cw-xcall-lib" } | ||
hex = "0.4.3" | ||
serde-json-wasm = {workspace=true} | ||
|
||
[dev-dependencies] | ||
cosmwasm = "0.7.2" | ||
getrandom = {version = "0.2", default-features = false, features = ["custom"]} | ||
|
217 changes: 217 additions & 0 deletions
217
contracts/cosmwasm-vm/cw-centralized-connection/src/contract.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
use cosmwasm_std::{coins, Addr, BankMsg, Event, SubMsgResult, Uint128}; | ||
use cw_xcall_lib::network_address::NetId; | ||
|
||
use super::*; | ||
|
||
// version info for migration info | ||
const CONTRACT_NAME: &str = "crates.io:cw-mock-dapp"; | ||
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); | ||
|
||
impl<'a> CwCentralizedConnection<'a> { | ||
pub fn instantiate( | ||
&mut self, | ||
deps: DepsMut, | ||
_env: Env, | ||
_info: MessageInfo, | ||
msg: InstantiateMsg, | ||
) -> Result<Response, ContractError> { | ||
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; | ||
|
||
let relayer = deps.api.addr_validate(&msg.relayer)?; | ||
self.store_admin(deps.storage, relayer)?; | ||
|
||
let xcall_address = deps.api.addr_validate(&msg.xcall_address)?; | ||
self.store_xcall(deps.storage, xcall_address)?; | ||
self.store_denom(deps.storage, msg.denom)?; | ||
|
||
let _ = self.store_conn_sn(deps.storage, 0); | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "instantiate") | ||
.add_attribute("relayer", msg.relayer) | ||
.add_attribute("xcall_address", msg.xcall_address)) | ||
} | ||
|
||
pub fn send_message( | ||
&mut self, | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
to: NetId, | ||
sn: i64, | ||
msg: Vec<u8>, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_xcall(deps.storage, info.sender)?; | ||
|
||
let next_conn_sn = self.get_next_conn_sn(deps.storage)?; | ||
self.store_conn_sn(deps.storage, next_conn_sn)?; | ||
|
||
let mut fee = 0; | ||
|
||
if sn >= 0 { | ||
fee = self.get_fee(deps.storage, to.clone(), sn > 0)?.into(); | ||
} | ||
|
||
let value = self.get_amount_for_denom(&info.funds, self.denom(deps.storage)); | ||
|
||
if fee > value { | ||
return Err(ContractError::InsufficientFunds); | ||
} | ||
|
||
Ok(Response::new() | ||
.add_attribute("action", "send_message") | ||
.add_event( | ||
Event::new("Message") | ||
.add_attribute("targetNetwork", to.to_string()) | ||
.add_attribute("connSn", next_conn_sn.to_string()) | ||
.add_attribute("msg", self.hex_encode(msg)), | ||
)) | ||
} | ||
|
||
pub fn recv_message( | ||
&mut self, | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
src_network: NetId, | ||
conn_sn: u128, | ||
msg: String, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_admin(deps.storage, info.sender)?; | ||
|
||
let hex_string_trimmed = msg.trim_start_matches("0x"); | ||
let bytes = hex::decode(hex_string_trimmed).expect("Failed to decode to vec<u8>"); | ||
|
||
let vec_msg: Vec<u8> = Binary(bytes).into(); | ||
if self.get_receipt(deps.as_ref().storage, src_network.clone(), conn_sn) { | ||
return Err(ContractError::DuplicateMessage); | ||
} | ||
self.store_receipt(deps.storage, src_network.clone(), conn_sn)?; | ||
|
||
let xcall_submessage = | ||
self.call_xcall_handle_message(deps.storage, &src_network, vec_msg)?; | ||
|
||
Ok(Response::new().add_submessage(xcall_submessage)) | ||
} | ||
|
||
pub fn claim_fees( | ||
&self, | ||
deps: DepsMut, | ||
env: Env, | ||
info: MessageInfo, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_admin(deps.storage, info.sender)?; | ||
let contract_balance = self.get_balance(&deps, env, self.denom(deps.storage)); | ||
let msg = BankMsg::Send { | ||
to_address: self.query_admin(deps.storage)?.to_string(), | ||
amount: coins(contract_balance, self.denom(deps.storage)), | ||
}; | ||
Ok(Response::new() | ||
.add_attribute("action", "claim fees") | ||
.add_message(msg)) | ||
} | ||
|
||
pub fn revert_message( | ||
&self, | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
sn: u128, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_admin(deps.storage, info.sender)?; | ||
let xcall_submessage = self.call_xcall_handle_error(deps.storage, sn)?; | ||
|
||
Ok(Response::new().add_submessage(xcall_submessage)) | ||
} | ||
|
||
pub fn set_admin( | ||
&mut self, | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
address: Addr, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_admin(deps.storage, info.sender)?; | ||
let admin = deps.api.addr_validate(address.as_str())?; | ||
let _ = self.store_admin(deps.storage, admin); | ||
Ok(Response::new().add_attribute("action", "set_admin")) | ||
} | ||
|
||
pub fn set_fee( | ||
&mut self, | ||
deps: DepsMut, | ||
info: MessageInfo, | ||
network_id: NetId, | ||
message_fee: u128, | ||
response_fee: u128, | ||
) -> Result<Response, ContractError> { | ||
self.ensure_admin(deps.storage, info.sender)?; | ||
self.store_fee(deps.storage, network_id, message_fee, response_fee)?; | ||
Ok(Response::new().add_attribute("action", "set_fee")) | ||
} | ||
|
||
pub fn get_fee( | ||
&self, | ||
store: &dyn Storage, | ||
network_id: NetId, | ||
response: bool, | ||
) -> Result<Uint128, ContractError> { | ||
let mut fee = self.query_message_fee(store, network_id.clone()); | ||
if response { | ||
fee += self.query_response_fee(store, network_id); | ||
} | ||
Ok(fee.into()) | ||
} | ||
|
||
fn xcall_handle_message_reply( | ||
&self, | ||
_deps: DepsMut, | ||
message: Reply, | ||
) -> Result<Response, ContractError> { | ||
println!("Reply From Forward XCall"); | ||
match message.result { | ||
SubMsgResult::Ok(_) => Ok(Response::new() | ||
.add_attribute("action", "call_message") | ||
.add_attribute("method", "xcall_handle_message_reply")), | ||
SubMsgResult::Err(error) => Err(ContractError::ReplyError { | ||
code: message.id, | ||
msg: error, | ||
}), | ||
} | ||
} | ||
|
||
fn xcall_handle_error_reply( | ||
&self, | ||
_deps: DepsMut, | ||
message: Reply, | ||
) -> Result<Response, ContractError> { | ||
println!("Reply From Forward XCall"); | ||
match message.result { | ||
SubMsgResult::Ok(_) => Ok(Response::new() | ||
.add_attribute("action", "call_message") | ||
.add_attribute("method", "xcall_handle_error_reply")), | ||
SubMsgResult::Err(error) => Err(ContractError::ReplyError { | ||
code: message.id, | ||
msg: error, | ||
}), | ||
} | ||
} | ||
|
||
pub fn reply(&self, deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> { | ||
match msg.id { | ||
XCALL_HANDLE_MESSAGE_REPLY_ID => self.xcall_handle_message_reply(deps, msg), | ||
XCALL_HANDLE_ERROR_REPLY_ID => self.xcall_handle_error_reply(deps, msg), | ||
_ => Err(ContractError::ReplyError { | ||
code: msg.id, | ||
msg: "Unknown".to_string(), | ||
}), | ||
} | ||
} | ||
|
||
pub fn migrate( | ||
&self, | ||
deps: DepsMut, | ||
_env: Env, | ||
_msg: MigrateMsg, | ||
) -> Result<Response, ContractError> { | ||
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) | ||
.map_err(ContractError::Std)?; | ||
Ok(Response::default().add_attribute("migrate", "successful")) | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
contracts/cosmwasm-vm/cw-centralized-connection/src/errors.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use super::*; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum ContractError { | ||
#[error("{0}")] | ||
Std(#[from] StdError), | ||
#[error("Unauthorized")] | ||
Unauthorized {}, | ||
#[error("Invalid Address {address}")] | ||
InvalidAddress { address: String }, | ||
#[error("Only Relayer(Admin)")] | ||
OnlyAdmin, | ||
#[error("Only XCall")] | ||
OnlyXCall, | ||
#[error("Duplicate Message")] | ||
DuplicateMessage, | ||
#[error("InsufficientFunds")] | ||
InsufficientFunds, | ||
#[error("ERR_REPLY_ERROR|{code:?}|{msg:?}")] | ||
ReplyError { code: u64, msg: String }, | ||
} |
85 changes: 85 additions & 0 deletions
85
contracts/cosmwasm-vm/cw-centralized-connection/src/helper.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use cosmwasm_std::{ensure_eq, Addr, BalanceResponse, BankQuery, Coin}; | ||
use cw_xcall_lib::network_address::NetId; | ||
|
||
pub const XCALL_HANDLE_MESSAGE_REPLY_ID: u64 = 1; | ||
pub const XCALL_HANDLE_ERROR_REPLY_ID: u64 = 2; | ||
use super::*; | ||
|
||
impl<'a> CwCentralizedConnection<'a> { | ||
pub fn ensure_admin(&self, store: &dyn Storage, address: Addr) -> Result<(), ContractError> { | ||
let admin = self.query_admin(store)?; | ||
ensure_eq!(admin, address, ContractError::OnlyAdmin); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn ensure_xcall(&self, store: &dyn Storage, address: Addr) -> Result<(), ContractError> { | ||
let xcall = self.query_xcall(store)?; | ||
ensure_eq!(xcall, address, ContractError::OnlyXCall); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn get_amount_for_denom(&self, funds: &Vec<Coin>, target_denom: String) -> u128 { | ||
for coin in funds.iter() { | ||
if coin.denom == target_denom { | ||
return coin.amount.into(); | ||
} | ||
} | ||
0 | ||
} | ||
|
||
pub fn get_balance(&self, deps: &DepsMut, env: Env, denom: String) -> u128 { | ||
let address = env.contract.address.to_string(); | ||
let balance_query = BankQuery::Balance { denom, address }; | ||
let balance_response: BalanceResponse = deps.querier.query(&balance_query.into()).unwrap(); | ||
|
||
balance_response.amount.amount.u128() | ||
} | ||
|
||
pub fn hex_encode(&self, data: Vec<u8>) -> String { | ||
if data.is_empty() { | ||
"null".to_string() | ||
} else { | ||
hex::encode(data) | ||
} | ||
} | ||
|
||
pub fn call_xcall_handle_message( | ||
&self, | ||
store: &dyn Storage, | ||
nid: &NetId, | ||
msg: Vec<u8>, | ||
) -> Result<SubMsg, ContractError> { | ||
let xcall_host = self.query_xcall(store)?; | ||
let xcall_msg = cw_xcall_lib::xcall_msg::ExecuteMsg::HandleMessage { | ||
from_nid: nid.clone(), | ||
msg, | ||
}; | ||
let call_message: CosmosMsg<Empty> = CosmosMsg::Wasm(WasmMsg::Execute { | ||
contract_addr: xcall_host.to_string(), | ||
msg: to_binary(&xcall_msg).unwrap(), | ||
funds: vec![], | ||
}); | ||
let sub_msg: SubMsg = SubMsg::reply_always(call_message, XCALL_HANDLE_MESSAGE_REPLY_ID); | ||
Ok(sub_msg) | ||
} | ||
|
||
pub fn call_xcall_handle_error( | ||
&self, | ||
store: &dyn Storage, | ||
sn: u128, | ||
) -> Result<SubMsg, ContractError> { | ||
let xcall_host = self.query_xcall(store)?; | ||
let xcall_msg = cw_xcall_lib::xcall_msg::ExecuteMsg::HandleError { | ||
sn: sn.try_into().unwrap(), | ||
}; | ||
let call_message: CosmosMsg<Empty> = CosmosMsg::Wasm(WasmMsg::Execute { | ||
contract_addr: xcall_host.to_string(), | ||
msg: to_binary(&xcall_msg).unwrap(), | ||
funds: vec![], | ||
}); | ||
let sub_msg: SubMsg = SubMsg::reply_always(call_message, XCALL_HANDLE_ERROR_REPLY_ID); | ||
Ok(sub_msg) | ||
} | ||
} |
Oops, something went wrong.