1use super::{addresses::AndrAddr, messages::AMPMsg};
2use crate::{ado_contract::ADOContract, common::encode_binary, error::ContractError};
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{to_json_binary, BankMsg, Binary, Coin, CosmosMsg, Deps, SubMsg, WasmMsg};
5use cw20::{Cw20Coin, Cw20ExecuteMsg};
6use serde::Serialize;
7
8#[cw_serde]
15pub struct Recipient {
16 pub address: AndrAddr,
17 pub msg: Option<Binary>,
18 pub ibc_recovery_address: Option<AndrAddr>,
19}
20
21impl Recipient {
22 pub fn new(addr: impl Into<String>, msg: Option<Binary>) -> Recipient {
23 Recipient {
24 address: AndrAddr::from_string(addr),
25 msg,
26 ibc_recovery_address: None,
27 }
28 }
29
30 pub fn validate(&self, deps: &Deps) -> Result<(), ContractError> {
32 self.address.validate(deps.api)?;
33 self.address.get_raw_address(deps)?;
34
35 if let Some(ibc_recovery_address) = self.ibc_recovery_address.clone() {
37 ibc_recovery_address.validate(deps.api)?;
38 ibc_recovery_address.get_raw_address(deps)?;
39 }
40
41 Ok(())
42 }
43
44 pub fn from_string(addr: impl Into<String>) -> Recipient {
46 Recipient {
47 address: AndrAddr::from_string(addr.into()),
48 msg: None,
49 ibc_recovery_address: None,
50 }
51 }
52
53 pub fn get_addr(&self) -> String {
54 self.address.to_string()
55 }
56
57 pub fn get_message(&self) -> Option<Binary> {
58 self.msg.clone()
59 }
60
61 pub fn generate_direct_msg(
63 &self,
64 deps: &Deps,
65 funds: Vec<Coin>,
66 ) -> Result<SubMsg, ContractError> {
67 let resolved_addr = self.address.get_raw_address(deps)?;
68 Ok(match &self.msg {
69 Some(message) => SubMsg::new(WasmMsg::Execute {
70 contract_addr: resolved_addr.to_string(),
71 msg: message.clone(),
72 funds,
73 }),
74 None => SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
75 to_address: resolved_addr.to_string(),
76 amount: funds,
77 })),
78 })
79 }
80
81 pub fn generate_msg_cw20(
86 &self,
87 deps: &Deps,
88 cw20_coin: Cw20Coin,
89 ) -> Result<SubMsg, ContractError> {
90 let resolved_addr = self.address.get_raw_address(deps)?;
91 Ok(match &self.msg {
92 Some(msg) => SubMsg::new(WasmMsg::Execute {
93 contract_addr: cw20_coin.address,
94 msg: encode_binary(&Cw20ExecuteMsg::Send {
95 contract: resolved_addr.to_string(),
96 amount: cw20_coin.amount,
97 msg: msg.clone(),
98 })?,
99 funds: vec![],
100 }),
101 None => SubMsg::new(WasmMsg::Execute {
102 contract_addr: cw20_coin.address,
103 msg: encode_binary(&Cw20ExecuteMsg::Transfer {
104 recipient: resolved_addr.to_string(),
105 amount: cw20_coin.amount,
106 })?,
107 funds: vec![],
108 }),
109 })
110 }
111
112 pub fn generate_amp_msg(
116 &self,
117 deps: &Deps,
118 funds: Option<Vec<Coin>>,
119 ) -> Result<AMPMsg, ContractError> {
120 let mut address = self.address.clone();
121 if address.is_local_path() {
122 let vfs_addr = ADOContract::default().get_vfs_address(deps.storage, &deps.querier)?;
123 address = address.local_path_to_vfs_path(deps.storage, &deps.querier, vfs_addr)?;
124 }
125 Ok(AMPMsg::new(
126 address.to_string(),
127 self.msg.clone().unwrap_or_default(),
128 funds,
129 )
130 .with_ibc_recovery(self.ibc_recovery_address.clone()))
131 }
132
133 pub fn with_ibc_recovery(self, addr: impl Into<String>) -> Self {
137 let mut new_recip = self;
138 new_recip.ibc_recovery_address = Some(AndrAddr::from_string(addr.into()));
139 new_recip
140 }
141
142 pub fn with_msg(self, msg: impl Serialize) -> Self {
144 let mut new_recip = self;
145 new_recip.msg = Some(to_json_binary(&msg).unwrap());
146 new_recip
147 }
148}
149
150#[cfg(test)]
151mod test {
152 use cosmwasm_std::{from_json, testing::mock_dependencies, Addr, Uint128};
153
154 use crate::testing::mock_querier::{mock_dependencies_custom, MOCK_APP_CONTRACT};
155
156 use super::*;
157
158 #[test]
159 fn test_generate_direct_msg() {
160 let deps = mock_dependencies();
161 let recipient = Recipient::from_string("test");
162 let funds = vec![Coin {
163 denom: "test".to_string(),
164 amount: Uint128::from(100u128),
165 }];
166 let msg = recipient
167 .generate_direct_msg(&deps.as_ref(), funds.clone())
168 .unwrap();
169 match msg.msg {
170 CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => {
171 assert_eq!(to_address, "test");
172 assert_eq!(amount, funds);
173 }
174 _ => panic!("Unexpected message type"),
175 }
176
177 let recipient = Recipient::new("test", Some(Binary::from(b"test".to_vec())));
178 let msg = recipient
179 .generate_direct_msg(&deps.as_ref(), funds.clone())
180 .unwrap();
181 match msg.msg {
182 CosmosMsg::Wasm(WasmMsg::Execute {
183 contract_addr,
184 msg,
185 funds: msg_funds,
186 }) => {
187 assert_eq!(contract_addr, "test");
188 assert_eq!(msg, Binary::from(b"test".to_vec()));
189 assert_eq!(msg_funds, funds);
190 }
191 _ => panic!("Unexpected message type"),
192 }
193 }
194
195 #[test]
196 fn test_generate_msg_cw20() {
197 let deps = mock_dependencies();
198 let recipient = Recipient::from_string("test");
199 let cw20_coin = Cw20Coin {
200 address: "test".to_string(),
201 amount: Uint128::from(100u128),
202 };
203 let msg = recipient
204 .generate_msg_cw20(&deps.as_ref(), cw20_coin.clone())
205 .unwrap();
206 match msg.msg {
207 CosmosMsg::Wasm(WasmMsg::Execute {
208 contract_addr,
209 msg,
210 funds,
211 }) => {
212 assert_eq!(contract_addr, "test");
213 assert_eq!(funds, vec![] as Vec<Coin>);
214 match from_json(msg).unwrap() {
215 Cw20ExecuteMsg::Transfer { recipient, amount } => {
216 assert_eq!(recipient, "test");
217 assert_eq!(amount, cw20_coin.amount);
218 }
219 _ => panic!("Unexpected message type"),
220 }
221 }
222 _ => panic!("Unexpected message type"),
223 }
224
225 let recipient = Recipient::new("test", Some(Binary::from(b"test".to_vec())));
226 let msg = recipient
227 .generate_msg_cw20(&deps.as_ref(), cw20_coin.clone())
228 .unwrap();
229 match msg.msg {
230 CosmosMsg::Wasm(WasmMsg::Execute {
231 contract_addr,
232 msg,
233 funds,
234 }) => {
235 assert_eq!(contract_addr, "test");
236 assert_eq!(funds, vec![] as Vec<Coin>);
237 match from_json(msg).unwrap() {
238 Cw20ExecuteMsg::Send {
239 contract,
240 amount,
241 msg: send_msg,
242 } => {
243 assert_eq!(contract, "test");
244 assert_eq!(amount, cw20_coin.amount);
245 assert_eq!(send_msg, Binary::from(b"test".to_vec()));
246 }
247 _ => panic!("Unexpected message type"),
248 }
249 }
250 _ => panic!("Unexpected message type"),
251 }
252 }
253
254 #[test]
255 fn test_generate_amp_msg() {
256 let recipient = Recipient::from_string("test");
257 let mut deps = mock_dependencies_custom(&[]);
258 let msg = recipient.generate_amp_msg(&deps.as_ref(), None).unwrap();
259 assert_eq!(msg.recipient, "test");
260 assert_eq!(msg.message, Binary::default());
261 assert_eq!(msg.funds, vec![] as Vec<Coin>);
262
263 let recipient = Recipient::new("test", Some(Binary::from(b"test".to_vec())));
264 let msg = recipient.generate_amp_msg(&deps.as_ref(), None).unwrap();
265 assert_eq!(msg.recipient, "test");
266 assert_eq!(msg.message, Binary::from(b"test".to_vec()));
267 assert_eq!(msg.funds, vec![] as Vec<Coin>);
268
269 let funds = vec![Coin {
270 denom: "test".to_string(),
271 amount: Uint128::from(100u128),
272 }];
273 let recipient = Recipient::from_string("test");
274 let msg = recipient
275 .generate_amp_msg(&deps.as_ref(), Some(funds.clone()))
276 .unwrap();
277 assert_eq!(msg.recipient, "test");
278 assert_eq!(msg.message, Binary::default());
279 assert_eq!(msg.funds, funds);
280
281 ADOContract::default()
282 .app_contract
283 .save(deps.as_mut().storage, &Addr::unchecked(MOCK_APP_CONTRACT))
284 .unwrap();
285 let recipient = Recipient::from_string("./test");
286 let msg = recipient
287 .generate_amp_msg(&deps.as_ref(), Some(funds.clone()))
288 .unwrap();
289 assert_eq!(
290 msg.recipient.to_string(),
291 format!("~{MOCK_APP_CONTRACT}/test")
292 );
293 assert_eq!(msg.message, Binary::default());
294 assert_eq!(msg.funds, funds);
295 }
296}