1use crate::ado_contract::ADOContract;
2use crate::common::encode_binary;
3use crate::error::ContractError;
4use crate::os::aos_querier::AOSQuerier;
5use crate::os::{kernel::ExecuteMsg as KernelExecuteMsg, kernel::QueryMsg as KernelQueryMsg};
6use cosmwasm_schema::cw_serde;
7use cosmwasm_std::{
8 to_json_binary, wasm_execute, Addr, Binary, Coin, ContractInfoResponse, CosmosMsg, Deps, Empty,
9 MessageInfo, QueryRequest, ReplyOn, SubMsg, WasmMsg, WasmQuery,
10};
11
12use super::addresses::AndrAddr;
13use super::ADO_DB_KEY;
14
15#[cw_serde]
17pub enum ExecuteMsg {
18 #[serde(rename = "amp_receive")]
20 AMPReceive(AMPPkt),
21}
22
23#[cw_serde]
24#[derive(Default)]
25pub struct IBCConfig {
26 pub recovery_addr: Option<AndrAddr>,
27}
28
29impl IBCConfig {
30 #[inline]
31 pub fn new(recovery_addr: Option<AndrAddr>) -> IBCConfig {
32 IBCConfig { recovery_addr }
33 }
34}
35
36#[cw_serde]
40pub struct AMPMsgConfig {
41 pub reply_on: ReplyOn,
43 pub exit_at_error: bool,
45 pub gas_limit: Option<u64>,
47 pub direct: bool,
49 pub ibc_config: Option<IBCConfig>,
50}
51
52impl AMPMsgConfig {
53 #[inline]
54 pub fn new(
55 reply_on: Option<ReplyOn>,
56 exit_at_error: Option<bool>,
57 gas_limit: Option<u64>,
58 ibc_config: Option<IBCConfig>,
59 ) -> AMPMsgConfig {
60 AMPMsgConfig {
61 reply_on: reply_on.unwrap_or(ReplyOn::Always),
62 exit_at_error: exit_at_error.unwrap_or(true),
63 gas_limit,
64 direct: false,
65 ibc_config,
66 }
67 }
68
69 pub fn as_direct_msg(self) -> AMPMsgConfig {
71 AMPMsgConfig {
72 reply_on: self.reply_on,
73 exit_at_error: self.exit_at_error,
74 gas_limit: self.gas_limit,
75 direct: true,
76 ibc_config: self.ibc_config,
77 }
78 }
79}
80
81impl Default for AMPMsgConfig {
82 #[inline]
83 fn default() -> AMPMsgConfig {
84 AMPMsgConfig {
85 reply_on: ReplyOn::Always,
86 exit_at_error: true,
87 gas_limit: None,
88 direct: false,
89 ibc_config: None,
90 }
91 }
92}
93
94#[cw_serde]
95pub struct AMPMsg {
100 pub recipient: AndrAddr,
102 pub message: Binary,
104 pub funds: Vec<Coin>,
106 pub config: AMPMsgConfig,
108}
109
110impl AMPMsg {
111 pub fn new(recipient: impl Into<String>, message: Binary, funds: Option<Vec<Coin>>) -> AMPMsg {
113 AMPMsg {
114 recipient: AndrAddr::from_string(recipient),
115 message,
116 funds: funds.unwrap_or_default(),
117 config: AMPMsgConfig::default(),
118 }
119 }
120
121 pub fn with_config(&self, config: AMPMsgConfig) -> AMPMsg {
122 AMPMsg {
123 recipient: self.recipient.clone(),
124 message: self.message.clone(),
125 funds: self.funds.clone(),
126 config,
127 }
128 }
129
130 pub fn generate_amp_pkt(
132 &self,
133 deps: &Deps,
134 origin: impl Into<String>,
135 previous_sender: impl Into<String>,
136 id: u64,
137 ) -> Result<SubMsg, ContractError> {
138 let contract_addr = self.recipient.get_raw_address(deps)?;
139 let pkt = AMPPkt::new(origin, previous_sender, vec![self.clone()]);
140 let msg = to_json_binary(&ExecuteMsg::AMPReceive(pkt))?;
141 Ok(SubMsg {
142 id,
143 reply_on: self.config.reply_on.clone(),
144 gas_limit: self.config.gas_limit,
145 msg: CosmosMsg::Wasm(WasmMsg::Execute {
146 contract_addr: contract_addr.into(),
147 msg,
148 funds: self.funds.to_vec(),
149 }),
150 })
151 }
152
153 pub fn generate_sub_msg_direct(&self, addr: Addr, id: u64) -> SubMsg<Empty> {
154 SubMsg {
155 id,
156 reply_on: self.config.reply_on.clone(),
157 gas_limit: self.config.gas_limit,
158 msg: CosmosMsg::Wasm(wasm_execute(addr, &self.message, self.funds.to_vec()).unwrap()),
159 }
160 }
161
162 pub fn to_ibc_hooks_memo(&self, contract_addr: String, callback_addr: String) -> String {
163 #[derive(::serde::Serialize)]
164 struct IbcHooksWasmMsg<T: ::serde::Serialize> {
165 contract: String,
166 msg: T,
167 }
168 #[derive(::serde::Serialize)]
169 struct IbcHooksMsg<T: ::serde::Serialize> {
170 wasm: IbcHooksWasmMsg<T>,
171 ibc_callback: String,
172 }
173 let wasm_msg = IbcHooksWasmMsg {
174 contract: contract_addr,
175 msg: KernelExecuteMsg::Send {
176 message: self.clone(),
177 },
178 };
179 let msg = IbcHooksMsg {
180 wasm: wasm_msg,
181 ibc_callback: callback_addr,
182 };
183
184 serde_json_wasm::to_string(&msg).unwrap()
185 }
186
187 pub fn with_ibc_recovery(&self, recovery_addr: Option<AndrAddr>) -> AMPMsg {
189 if let Some(ibc_config) = self.config.ibc_config.clone() {
190 let mut ibc_config = ibc_config;
191 ibc_config.recovery_addr = recovery_addr;
192 let mut msg = self.clone();
193 msg.config.ibc_config = Some(ibc_config);
194 msg
195 } else if let Some(recovery_addr) = recovery_addr {
196 let ibc_config = Some(IBCConfig {
197 recovery_addr: Some(recovery_addr),
198 });
199 let mut msg = self.clone();
200 msg.config.ibc_config = ibc_config;
201 msg
202 } else {
203 self.clone()
204 }
205 }
206}
207
208#[cw_serde]
209pub struct AMPCtx {
210 origin: String,
211 origin_username: Option<AndrAddr>,
212 pub previous_sender: String,
213 pub id: u64,
214}
215
216impl AMPCtx {
217 #[inline]
218 pub fn new(
219 origin: impl Into<String>,
220 previous_sender: impl Into<String>,
221 id: u64,
222 origin_username: Option<AndrAddr>,
223 ) -> AMPCtx {
224 AMPCtx {
225 origin: origin.into(),
226 origin_username,
227 previous_sender: previous_sender.into(),
228 id,
229 }
230 }
231
232 pub fn get_origin(&self) -> String {
234 self.origin.clone()
235 }
236
237 pub fn get_previous_sender(&self) -> String {
239 self.previous_sender.clone()
240 }
241}
242
243#[cw_serde]
244pub struct AMPPkt {
250 pub messages: Vec<AMPMsg>,
252 pub ctx: AMPCtx,
253}
254
255impl AMPPkt {
256 pub fn new(
258 origin: impl Into<String>,
259 previous_sender: impl Into<String>,
260 messages: Vec<AMPMsg>,
261 ) -> AMPPkt {
262 AMPPkt {
263 messages,
264 ctx: AMPCtx::new(origin, previous_sender, 0, None),
265 }
266 }
267
268 pub fn add_message(mut self, message: AMPMsg) -> Self {
270 self.messages.push(message);
271 self
272 }
273
274 pub fn get_unique_recipients(&self) -> Vec<String> {
276 let mut recipients: Vec<String> = self
277 .messages
278 .iter()
279 .cloned()
280 .map(|msg| msg.recipient.to_string())
281 .collect();
282 recipients.sort_unstable();
283 recipients.dedup();
284 recipients
285 }
286
287 pub fn get_messages_for_recipient(&self, recipient: String) -> Vec<AMPMsg> {
289 self.messages
290 .iter()
291 .cloned()
292 .filter(|msg| msg.recipient == recipient.clone())
293 .collect()
294 }
295
296 pub fn verify_origin(&self, info: &MessageInfo, deps: &Deps) -> Result<(), ContractError> {
305 let kernel_address = ADOContract::default().get_kernel_address(deps.storage)?;
306 if info.sender == self.ctx.origin || info.sender == kernel_address {
307 Ok(())
308 } else {
309 let adodb_address: Addr =
310 deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
311 contract_addr: kernel_address.to_string(),
312 msg: to_json_binary(&KernelQueryMsg::KeyAddress {
313 key: ADO_DB_KEY.to_string(),
314 })?,
315 }))?;
316
317 let contract_info: ContractInfoResponse =
319 deps.querier
320 .query(&QueryRequest::Wasm(WasmQuery::ContractInfo {
321 contract_addr: info.sender.to_string(),
322 }))?;
323
324 let sender_code_id = contract_info.code_id;
325
326 AOSQuerier::verify_code_id(&deps.querier, &adodb_address, sender_code_id)
328 }
329 }
330
331 pub fn get_verified_origin(
333 &self,
334 info: &MessageInfo,
335 deps: &Deps,
336 ) -> Result<String, ContractError> {
337 let origin = self.ctx.get_origin();
338 let res = self.verify_origin(info, deps);
339 match res {
340 Ok(_) => Ok(origin),
341 Err(err) => Err(err),
342 }
343 }
344
345 pub fn to_sub_msg(
348 &self,
349 address: impl Into<String>,
350 funds: Option<Vec<Coin>>,
351 id: u64,
352 ) -> Result<SubMsg, ContractError> {
353 let sub_msg = SubMsg::reply_always(
354 WasmMsg::Execute {
355 contract_addr: address.into(),
356 msg: encode_binary(&KernelExecuteMsg::AMPReceive(self.clone()))?,
357 funds: funds.unwrap_or_default(),
358 },
359 id,
360 );
361 Ok(sub_msg)
362 }
363
364 pub fn with_id(&self, id: u64) -> AMPPkt {
366 let mut new = self.clone();
367 new.ctx.id = id;
368 new
369 }
370
371 pub fn to_ibc_hooks_memo(&self, contract_addr: String, callback_addr: String) -> String {
373 #[derive(::serde::Serialize)]
374 struct IbcHooksWasmMsg<T: ::serde::Serialize> {
375 contract: String,
376 msg: T,
377 }
378 #[derive(::serde::Serialize)]
379 struct IbcHooksMsg<T: ::serde::Serialize> {
380 wasm: IbcHooksWasmMsg<T>,
381 ibc_callback: String,
382 }
383 let wasm_msg = IbcHooksWasmMsg {
384 contract: contract_addr,
385 msg: KernelExecuteMsg::AMPReceive(self.clone()),
386 };
387 let msg = IbcHooksMsg {
388 wasm: wasm_msg,
389 ibc_callback: callback_addr,
390 };
391
392 serde_json_wasm::to_string(&msg).unwrap()
393 }
394
395 pub fn to_json(&self) -> String {
397 serde_json_wasm::to_string(&self).unwrap()
398 }
399
400 pub fn from_ctx(ctx: Option<AMPPkt>, current_address: String) -> Self {
402 let mut ctx = if let Some(pkt) = ctx {
403 pkt.ctx
404 } else {
405 AMPCtx::new(current_address.clone(), current_address.clone(), 0, None)
406 };
407 ctx.previous_sender = current_address;
408
409 Self {
410 messages: vec![],
411 ctx,
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use cosmwasm_std::testing::{mock_dependencies, mock_info};
419
420 use crate::testing::mock_querier::{mock_dependencies_custom, INVALID_CONTRACT};
421
422 use super::*;
423
424 #[test]
425 fn test_generate_amp_pkt() {
426 let deps = mock_dependencies();
427 let msg = AMPMsg::new("test", Binary::default(), None);
428
429 let sub_msg = msg
430 .generate_amp_pkt(&deps.as_ref(), "origin", "previoussender", 1)
431 .unwrap();
432
433 let expected_msg = ExecuteMsg::AMPReceive(AMPPkt::new(
434 "origin",
435 "previoussender",
436 vec![AMPMsg::new("test", Binary::default(), None)],
437 ));
438 assert_eq!(sub_msg.id, 1);
439 assert_eq!(sub_msg.reply_on, ReplyOn::Always);
440 assert_eq!(sub_msg.gas_limit, None);
441 assert_eq!(
442 sub_msg.msg,
443 CosmosMsg::Wasm(WasmMsg::Execute {
444 contract_addr: "test".to_string(),
445 msg: to_json_binary(&expected_msg).unwrap(),
446 funds: vec![],
447 })
448 );
449 }
450
451 #[test]
452 fn test_get_unique_recipients() {
453 let msg = AMPMsg::new("test", Binary::default(), None);
454 let msg2 = AMPMsg::new("test2", Binary::default(), None);
455
456 let mut pkt = AMPPkt::new("origin", "previoussender", vec![msg, msg2]);
457
458 let recipients = pkt.get_unique_recipients();
459 assert_eq!(recipients.len(), 2);
460 assert_eq!(recipients[0], "test".to_string());
461 assert_eq!(recipients[1], "test2".to_string());
462
463 pkt = pkt.add_message(AMPMsg::new("test", Binary::default(), None));
464 let recipients = pkt.get_unique_recipients();
465 assert_eq!(recipients.len(), 2);
466 assert_eq!(recipients[0], "test".to_string());
467 assert_eq!(recipients[1], "test2".to_string());
468 }
469
470 #[test]
471 fn test_get_messages_for_recipient() {
472 let msg = AMPMsg::new("test", Binary::default(), None);
473 let msg2 = AMPMsg::new("test2", Binary::default(), None);
474
475 let mut pkt = AMPPkt::new("origin", "previoussender", vec![msg, msg2]);
476
477 let messages = pkt.get_messages_for_recipient("test".to_string());
478 assert_eq!(messages.len(), 1);
479 assert_eq!(messages[0].recipient.to_string(), "test".to_string());
480
481 let messages = pkt.get_messages_for_recipient("test2".to_string());
482 assert_eq!(messages.len(), 1);
483 assert_eq!(messages[0].recipient.to_string(), "test2".to_string());
484
485 pkt = pkt.add_message(AMPMsg::new("test", Binary::default(), None));
486 let messages = pkt.get_messages_for_recipient("test".to_string());
487 assert_eq!(messages.len(), 2);
488 assert_eq!(messages[0].recipient.to_string(), "test".to_string());
489 assert_eq!(messages[1].recipient.to_string(), "test".to_string());
490 }
491
492 #[test]
493 fn test_verify_origin() {
494 let deps = mock_dependencies_custom(&[]);
495 let msg = AMPMsg::new("test", Binary::default(), None);
496
497 let pkt = AMPPkt::new("origin", "previoussender", vec![msg.clone()]);
498
499 let info = mock_info("validaddress", &[]);
500 let res = pkt.verify_origin(&info, &deps.as_ref());
501 assert!(res.is_ok());
502
503 let info = mock_info(INVALID_CONTRACT, &[]);
504 let res = pkt.verify_origin(&info, &deps.as_ref());
505 assert!(res.is_err());
506
507 let offchain_pkt = AMPPkt::new(INVALID_CONTRACT, INVALID_CONTRACT, vec![msg]);
508 let res = offchain_pkt.verify_origin(&info, &deps.as_ref());
509 assert!(res.is_ok());
510 }
511
512 #[test]
513 fn test_to_sub_msg() {
514 let msg = AMPMsg::new("test", Binary::default(), None);
515
516 let pkt = AMPPkt::new("origin", "previoussender", vec![msg.clone()]);
517
518 let sub_msg = pkt.to_sub_msg("kernel", None, 1).unwrap();
519
520 let expected_msg =
521 ExecuteMsg::AMPReceive(AMPPkt::new("origin", "previoussender", vec![msg]));
522 assert_eq!(sub_msg.id, 1);
523 assert_eq!(sub_msg.reply_on, ReplyOn::Always);
524 assert_eq!(sub_msg.gas_limit, None);
525 assert_eq!(
526 sub_msg.msg,
527 CosmosMsg::Wasm(WasmMsg::Execute {
528 contract_addr: "kernel".to_string(),
529 msg: to_json_binary(&expected_msg).unwrap(),
530 funds: vec![],
531 })
532 );
533 }
534 #[test]
535 fn test_to_json() {
536 let msg = AMPPkt::new("origin", "previoussender", vec![]);
537
538 let memo = msg.to_json();
539 assert_eq!(memo, "{\"messages\":[],\"ctx\":{\"origin\":\"origin\",\"origin_username\":null,\"previous_sender\":\"previoussender\",\"id\":0}}".to_string());
540 }
541
542 #[test]
543 fn test_to_ibc_hooks_memo() {
544 let msg = AMPPkt::new("origin", "previoussender", vec![]);
545 let contract_addr = "contractaddr";
546 let memo = msg.to_ibc_hooks_memo(contract_addr.to_string(), "callback".to_string());
547 assert_eq!(memo, "{\"wasm\":{\"contract\":\"contractaddr\",\"msg\":{\"amp_receive\":{\"messages\":[],\"ctx\":{\"origin\":\"origin\",\"origin_username\":null,\"previous_sender\":\"previoussender\",\"id\":0}}}},\"ibc_callback\":\"callback\"}".to_string());
548 }
549}