Skip to content

Commit

Permalink
feat: add centralized connection cosmwasm (#233)
Browse files Browse the repository at this point in the history
* 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
gcranju authored Jan 17, 2024
1 parent 47ecc13 commit e0e0c75
Show file tree
Hide file tree
Showing 11 changed files with 911 additions and 0 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

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

45 changes: 45 additions & 0 deletions contracts/cosmwasm-vm/cw-centralized-connection/Cargo.toml
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 contracts/cosmwasm-vm/cw-centralized-connection/src/contract.rs
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 contracts/cosmwasm-vm/cw-centralized-connection/src/errors.rs
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 contracts/cosmwasm-vm/cw-centralized-connection/src/helper.rs
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)
}
}
Loading

0 comments on commit e0e0c75

Please sign in to comment.