1use cosmos_sdk_proto::tendermint::p2p::packet;
4use cosmwasm_std::entry_point;
5use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Event, Empty, CosmosMsg, IbcQuery};
6use crate::ibc::types::stargate::channel::new_ica_channel_open_init_cosmos_msg;
7use crate::types::keys::{self, CONTRACT_NAME, CONTRACT_VERSION};
8use crate::types::msg::{OutpostFactoryExecuteMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
9use crate::types::state::{
10 self, CallbackCounter, ChannelState, ContractState, CALLBACK_COUNTER, CHANNEL_STATE, STATE, CHANNEL_OPEN_INIT_OPTIONS, ALLOW_CHANNEL_OPEN_INIT
11};
12use crate::types::ContractError;
13use crate::types::filetree::{MsgPostKey, MsgPostFile};
14use crate::helpers::filetree_helpers::{hash_and_hex, merkle_helper};
15
16
17#[cfg(not(feature = "no_exports"))]
21#[entry_point]
22pub fn instantiate(
23 deps: DepsMut,
24 env: Env,
25 info: MessageInfo,
26 msg: InstantiateMsg, ) -> Result<Response, ContractError> {
28 use cosmwasm_std::WasmMsg;
29
30 cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
31
32 let owner = msg.owner.unwrap_or_else(|| info.sender.to_string());
40 cw_ownable::initialize_owner(deps.storage, deps.api, Some(&owner))?;
41
42 let admin = if let Some(admin) = msg.admin {
45 deps.api.addr_validate(&admin)?
46 } else {
47 info.sender.clone()
48 };
49
50 let mut event = Event::new("OUTPOST:instantiate");
51 event = event.add_attribute("info.sender", info.sender.clone());
52 event = event.add_attribute("outpost_address", env.contract.address.to_string());
53
54 STATE.save(deps.storage, &ContractState::new(admin))?;
62
63 CALLBACK_COUNTER.save(deps.storage, &CallbackCounter::default())?;
67
68 if let Some(ref options) = msg.channel_open_init_options {
69 CHANNEL_OPEN_INIT_OPTIONS.save(deps.storage, options)?;
70 }
71
72 ALLOW_CHANNEL_OPEN_INIT.save(deps.storage, &true)?;
73
74 if let Some(channel_open_init_options) = msg.channel_open_init_options {
76 let ica_channel_open_init_msg = new_ica_channel_open_init_cosmos_msg(
77 env.contract.address.to_string(),
78 channel_open_init_options.connection_id,
79 channel_open_init_options.counterparty_port_id,
80 channel_open_init_options.counterparty_connection_id,
81 channel_open_init_options.tx_encoding,
82 channel_open_init_options.channel_ordering,
83 );
84
85 let callback_factory_msg = if let Some(callback) = &msg.callback {
87
88 Some(CosmosMsg::Wasm(WasmMsg::Execute {
89 contract_addr: callback.contract.clone(),
90 msg: to_json_binary(&OutpostFactoryExecuteMsg::MapUserOutpost {
91 outpost_owner: callback.outpost_owner.clone(),
92 }).ok().expect("Failed to serialize callback_msg"),
93 funds: vec![],
94 }))
95 } else {
96 None
97 };
98
99 let mut messages: Vec<CosmosMsg> = Vec::new();
100 messages.push(ica_channel_open_init_msg);
101 if let Some(msg) = callback_factory_msg {
102 messages.push(msg)
103 }
104
105 Ok(Response::new().add_messages(messages).add_event(event).add_attribute("outpost_address", env.contract.address.to_string()))
106 } else {
107 Ok(Response::default())
108 }
109}
110
111#[cfg(not(feature = "no_exports"))]
113#[entry_point]
114pub fn execute(
115 deps: DepsMut,
116 env: Env,
117 info: MessageInfo,
118 msg: ExecuteMsg,
119) -> Result<Response, ContractError> {
120 match msg {
121 ExecuteMsg::CreateChannel {
122 channel_open_init_options,
123 } => execute::create_channel(deps, env, info, channel_open_init_options),
124 ExecuteMsg::SendCosmosMsgs {
125 messages,
126 packet_memo,
127 timeout_seconds,
128 } => {
129 execute::send_cosmos_msgs(deps, env, info, messages, packet_memo, timeout_seconds)
130 },
131 }
132}
133
134#[cfg(not(feature = "no_exports"))]
136#[entry_point]
137pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
138 match msg {
139 QueryMsg::GetContractState {} => to_json_binary(&query::state(deps)?),
140 QueryMsg::GetChannel {} => to_json_binary(&query::channel(deps)?),
141 QueryMsg::GetCallbackCounter {} => to_json_binary(&query::callback_counter(deps)?),
142 QueryMsg::Ownership {} => to_json_binary(&query::get_owner(deps)?),
143 }
144}
145
146#[cfg(not(feature = "no_exports"))]
148#[entry_point]
149pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
150 migrate::validate_semver(deps.as_ref())?;
151
152 cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
153 Ok(Response::default())
157}
158
159mod execute {
160 use cosmwasm_std::{coin, coins, BankMsg, CosmosMsg, IbcMsg, IbcTimeout, IbcTimeoutBlock, StdResult};
161 use prost::Message;
162
163 use crate::{
164 ibc::types::{metadata::TxEncoding, packet::IcaPacketData, stargate::channel},
165 types::msg::options::ChannelOpenInitOptions,
166 };
167
168 use cosmos_sdk_proto::cosmos::{bank::v1beta1::MsgSend, base::v1beta1::Coin};
169 use cosmos_sdk_proto::Any;
170
171 use super::*;
172
173 pub fn create_channel(
178 deps: DepsMut,
179 env: Env,
180 info: MessageInfo,
181 options: Option<ChannelOpenInitOptions>,
182 ) -> Result<Response, ContractError> {
183 cw_ownable::assert_owner(deps.storage, &info.sender)?;
184
185 let options = if let Some(new_options) = options {
186 state::CHANNEL_OPEN_INIT_OPTIONS.save(deps.storage, &new_options)?;
187 new_options
188 } else {
189 state::CHANNEL_OPEN_INIT_OPTIONS
190 .may_load(deps.storage)?
191 .ok_or(ContractError::NoChannelInitOptions)?
192 };
193
194 state::ALLOW_CHANNEL_OPEN_INIT.save(deps.storage, &true)?;
195
196 let ica_channel_open_init_msg = new_ica_channel_open_init_cosmos_msg(
197 env.contract.address.to_string(),
198 options.connection_id,
199 options.counterparty_port_id,
200 options.counterparty_connection_id,
201 options.tx_encoding, options.channel_ordering,
203 );
204
205 Ok(Response::new().add_message(ica_channel_open_init_msg))
206 }
207
208 #[allow(clippy::needless_pass_by_value)]
210 pub fn send_cosmos_msgs(
211 deps: DepsMut,
212 env: Env,
213 info: MessageInfo,
214 messages: Vec<CosmosMsg>,
215 packet_memo: Option<String>,
216 timeout_seconds: Option<u64>,
217 ) -> Result<Response, ContractError> {
219
220 cw_ownable::assert_owner(deps.storage, &info.sender)?;
224
225 let contract_state = STATE.load(deps.storage)?;
226 let ica_info = contract_state.get_ica_info()?;
227
228 let ica_packet = IcaPacketData::from_cosmos_msgs(
229 messages,
230 &ica_info.encoding,
231 packet_memo,
232 &ica_info.ica_address,
233 )?;
234 let send_packet_msg = ica_packet.to_ibc_msg(&env, ica_info.channel_id, timeout_seconds)?;
235
236 Ok(Response::default().add_message(send_packet_msg))
237
238 }
239}
240
241
242
243mod query {
244 use std::error::Error;
245
246 use cosmwasm_std::StdError;
247
248 use super::*;
249
250 pub fn state(deps: Deps) -> StdResult<ContractState> {
252 STATE.load(deps.storage)
253 }
254
255 pub fn channel(deps: Deps) -> StdResult<ChannelState> {
257 CHANNEL_STATE.load(deps.storage)
258 }
259
260 pub fn callback_counter(deps: Deps) -> StdResult<CallbackCounter> {
262 CALLBACK_COUNTER.load(deps.storage)
263 }
264
265 pub fn get_owner(deps: Deps) -> StdResult<String> {
267 let ownership = cw_ownable::get_ownership(deps.storage)?;
268
269 if let Some(owner) = ownership.owner {
270 Ok(owner.to_string())
271 } else {
272 Err(StdError::generic_err("No owner found"))
273 }
274 }
275}
276
277mod migrate {
278 use super::{keys, state, ContractError, Deps};
279
280 pub fn validate_semver(deps: Deps) -> Result<(), ContractError> {
283 let prev_cw2_version = cw2::get_contract_version(deps.storage)?;
284 if prev_cw2_version.contract != keys::CONTRACT_NAME {
285 return Err(ContractError::InvalidMigrationVersion {
286 expected: keys::CONTRACT_NAME.to_string(),
287 actual: prev_cw2_version.contract,
288 });
289 }
290
291 let version: semver::Version = keys::CONTRACT_VERSION.parse()?;
292 let prev_version: semver::Version = prev_cw2_version.version.parse()?;
293 if prev_version >= version {
294 return Err(ContractError::InvalidMigrationVersion {
295 expected: format!("> {prev_version}"),
296 actual: keys::CONTRACT_VERSION.to_string(),
297 });
298 }
299 Ok(())
300 }
301
302 pub fn validate_channel_encoding(deps: Deps) -> Result<(), ContractError> {
304 if let Some(ica_info) = state::STATE.load(deps.storage)?.ica_info {
306 if !matches!(
307 ica_info.encoding,
308 crate::ibc::types::metadata::TxEncoding::Protobuf
309 ) {
310 return Err(ContractError::UnsupportedPacketEncoding(
311 ica_info.encoding.to_string(),
312 ));
313 }
314 }
315
316 Ok(())
317 }
318}