github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/dev/wasm/escrow/src/contract.rs (about) 1 use cosmwasm_std::{ 2 entry_point, to_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Response, 3 StdResult, 4 }; 5 6 use crate::error::ContractError; 7 use crate::msg::{ArbiterResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; 8 use crate::state::{config, config_read, State}; 9 10 #[entry_point] 11 pub fn instantiate( 12 deps: DepsMut, 13 env: Env, 14 info: MessageInfo, 15 msg: InstantiateMsg, 16 ) -> Result<Response, ContractError> { 17 let state = State { 18 arbiter: deps.api.addr_validate(&msg.arbiter)?, 19 recipient: deps.api.addr_validate(&msg.recipient)?, 20 source: info.sender, 21 end_height: msg.end_height, 22 end_time: msg.end_time, 23 }; 24 25 if state.is_expired(&env) { 26 return Err(ContractError::Expired { 27 end_height: msg.end_height, 28 end_time: msg.end_time, 29 }); 30 } 31 32 config(deps.storage).save(&state)?; 33 Ok(Response::default()) 34 } 35 36 #[entry_point] 37 pub fn execute( 38 deps: DepsMut, 39 env: Env, 40 info: MessageInfo, 41 msg: ExecuteMsg, 42 ) -> Result<Response, ContractError> { 43 let state = config_read(deps.storage).load()?; 44 match msg { 45 ExecuteMsg::Approve { quantity } => try_approve(deps, env, state, info, quantity), 46 ExecuteMsg::Refund {} => try_refund(deps, env, info, state), 47 } 48 } 49 50 fn try_approve( 51 deps: DepsMut, 52 env: Env, 53 state: State, 54 info: MessageInfo, 55 quantity: Option<Vec<Coin>>, 56 ) -> Result<Response, ContractError> { 57 if info.sender != state.arbiter { 58 return Err(ContractError::Unauthorized {}); 59 } 60 61 // throws error if state is expired 62 if state.is_expired(&env) { 63 return Err(ContractError::Expired { 64 end_height: state.end_height, 65 end_time: state.end_time, 66 }); 67 } 68 69 let amount = if let Some(quantity) = quantity { 70 quantity 71 } else { 72 // release everything 73 74 // Querier guarantees to returns up-to-date data, including funds sent in this handle message 75 // https://github.com/CosmWasm/wasmd/blob/master/x/wasm/internal/keeper/keeper.go#L185-L192 76 deps.querier.query_all_balances(&env.contract.address)? 77 }; 78 79 Ok(send_tokens(state.recipient, amount, "approve")) 80 } 81 82 fn try_refund( 83 deps: DepsMut, 84 env: Env, 85 _info: MessageInfo, 86 state: State, 87 ) -> Result<Response, ContractError> { 88 // anyone can try to refund, as long as the contract is expired 89 if !state.is_expired(&env) { 90 return Err(ContractError::NotExpired {}); 91 } 92 93 // Querier guarantees to returns up-to-date data, including funds sent in this handle message 94 // https://github.com/CosmWasm/wasmd/blob/master/x/wasm/internal/keeper/keeper.go#L185-L192 95 let balance = deps.querier.query_all_balances(&env.contract.address)?; 96 Ok(send_tokens(state.source, balance, "refund")) 97 } 98 99 // this is a helper to move the tokens, so the business logic is easy to read 100 fn send_tokens(to_address: Addr, amount: Vec<Coin>, action: &str) -> Response { 101 Response::new() 102 .add_message(BankMsg::Send { 103 to_address: to_address.clone().into(), 104 amount, 105 }) 106 .add_attribute("action", action) 107 .add_attribute("to", to_address) 108 } 109 110 #[entry_point] 111 pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { 112 match msg { 113 QueryMsg::Arbiter {} => to_binary(&query_arbiter(deps)?), 114 } 115 } 116 117 fn query_arbiter(deps: Deps) -> StdResult<ArbiterResponse> { 118 let state = config_read(deps.storage).load()?; 119 let addr = state.arbiter; 120 Ok(ArbiterResponse { arbiter: addr }) 121 } 122 123 #[cfg(test)] 124 mod tests { 125 use super::*; 126 use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; 127 use cosmwasm_std::{coins, CosmosMsg, Timestamp}; 128 129 fn init_msg_expire_by_height(height: u64) -> InstantiateMsg { 130 InstantiateMsg { 131 arbiter: String::from("verifies"), 132 recipient: String::from("benefits"), 133 end_height: Some(height), 134 end_time: None, 135 } 136 } 137 138 #[test] 139 fn proper_initialization() { 140 let mut deps = mock_dependencies(&[]); 141 142 let msg = init_msg_expire_by_height(1000); 143 let mut env = mock_env(); 144 env.block.height = 876; 145 env.block.time = Timestamp::from_seconds(0); 146 let info = mock_info("creator", &coins(1000, "earth")); 147 148 let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); 149 assert_eq!(0, res.messages.len()); 150 151 // it worked, let's query the state 152 let state = config_read(&mut deps.storage).load().unwrap(); 153 assert_eq!( 154 state, 155 State { 156 arbiter: Addr::unchecked("verifies"), 157 recipient: Addr::unchecked("benefits"), 158 source: Addr::unchecked("creator"), 159 end_height: Some(1000), 160 end_time: None, 161 } 162 ); 163 } 164 165 #[test] 166 fn cannot_initialize_expired() { 167 let mut deps = mock_dependencies(&[]); 168 169 let msg = init_msg_expire_by_height(1000); 170 let mut env = mock_env(); 171 env.block.height = 1001; 172 env.block.time = Timestamp::from_seconds(0); 173 let info = mock_info("creator", &coins(1000, "earth")); 174 175 let res = instantiate(deps.as_mut(), env, info, msg); 176 match res.unwrap_err() { 177 ContractError::Expired { .. } => {} 178 e => panic!("unexpected error: {:?}", e), 179 } 180 } 181 182 #[test] 183 fn init_and_query() { 184 let mut deps = mock_dependencies(&[]); 185 186 let arbiter = Addr::unchecked("arbiters"); 187 let recipient = Addr::unchecked("receives"); 188 let creator = Addr::unchecked("creates"); 189 let msg = InstantiateMsg { 190 arbiter: arbiter.clone().into(), 191 recipient: recipient.into(), 192 end_height: None, 193 end_time: None, 194 }; 195 let mut env = mock_env(); 196 env.block.height = 876; 197 env.block.time = Timestamp::from_seconds(0); 198 let info = mock_info(creator.as_str(), &[]); 199 let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); 200 assert_eq!(0, res.messages.len()); 201 202 // now let's query 203 let query_response = query_arbiter(deps.as_ref()).unwrap(); 204 assert_eq!(query_response.arbiter, arbiter); 205 } 206 207 #[test] 208 fn execute_approve() { 209 let mut deps = mock_dependencies(&[]); 210 211 // initialize the store 212 let init_amount = coins(1000, "earth"); 213 let msg = init_msg_expire_by_height(1000); 214 let mut env = mock_env(); 215 env.block.height = 876; 216 env.block.time = Timestamp::from_seconds(0); 217 let info = mock_info("creator", &init_amount); 218 let contract_addr = env.clone().contract.address; 219 let init_res = instantiate(deps.as_mut(), env, info, msg).unwrap(); 220 assert_eq!(0, init_res.messages.len()); 221 222 // balance changed in init 223 deps.querier.update_balance(&contract_addr, init_amount); 224 225 // beneficiary cannot release it 226 let msg = ExecuteMsg::Approve { quantity: None }; 227 let mut env = mock_env(); 228 env.block.height = 900; 229 env.block.time = Timestamp::from_seconds(0); 230 let info = mock_info("beneficiary", &[]); 231 let execute_res = execute(deps.as_mut(), env, info, msg.clone()); 232 match execute_res.unwrap_err() { 233 ContractError::Unauthorized { .. } => {} 234 e => panic!("unexpected error: {:?}", e), 235 } 236 237 // verifier cannot release it when expired 238 let mut env = mock_env(); 239 env.block.height = 1100; 240 env.block.time = Timestamp::from_seconds(0); 241 let info = mock_info("verifies", &[]); 242 let execute_res = execute(deps.as_mut(), env, info, msg.clone()); 243 match execute_res.unwrap_err() { 244 ContractError::Expired { .. } => {} 245 e => panic!("unexpected error: {:?}", e), 246 } 247 248 // complete release by verfier, before expiration 249 let mut env = mock_env(); 250 env.block.height = 999; 251 env.block.time = Timestamp::from_seconds(0); 252 let info = mock_info("verifies", &[]); 253 let execute_res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); 254 assert_eq!(1, execute_res.messages.len()); 255 let msg = execute_res.messages.get(0).expect("no message"); 256 assert_eq!( 257 msg.msg, 258 CosmosMsg::Bank(BankMsg::Send { 259 to_address: "benefits".into(), 260 amount: coins(1000, "earth"), 261 }) 262 ); 263 264 // partial release by verfier, before expiration 265 let partial_msg = ExecuteMsg::Approve { 266 quantity: Some(coins(500, "earth")), 267 }; 268 let mut env = mock_env(); 269 env.block.height = 999; 270 env.block.time = Timestamp::from_seconds(0); 271 let info = mock_info("verifies", &[]); 272 let execute_res = execute(deps.as_mut(), env, info, partial_msg).unwrap(); 273 assert_eq!(1, execute_res.messages.len()); 274 let msg = execute_res.messages.get(0).expect("no message"); 275 assert_eq!( 276 msg.msg, 277 CosmosMsg::Bank(BankMsg::Send { 278 to_address: "benefits".into(), 279 amount: coins(500, "earth"), 280 }) 281 ); 282 } 283 284 #[test] 285 fn handle_refund() { 286 let mut deps = mock_dependencies(&[]); 287 288 // initialize the store 289 let init_amount = coins(1000, "earth"); 290 let msg = init_msg_expire_by_height(1000); 291 let mut env = mock_env(); 292 env.block.height = 876; 293 env.block.time = Timestamp::from_seconds(0); 294 let info = mock_info("creator", &init_amount); 295 let contract_addr = env.clone().contract.address; 296 let init_res = instantiate(deps.as_mut(), env, info, msg).unwrap(); 297 assert_eq!(0, init_res.messages.len()); 298 299 // balance changed in init 300 deps.querier.update_balance(&contract_addr, init_amount); 301 302 // cannot release when unexpired (height < end_height) 303 let msg = ExecuteMsg::Refund {}; 304 let mut env = mock_env(); 305 env.block.height = 800; 306 env.block.time = Timestamp::from_seconds(0); 307 let info = mock_info("anybody", &[]); 308 let execute_res = execute(deps.as_mut(), env, info, msg.clone()); 309 match execute_res.unwrap_err() { 310 ContractError::NotExpired { .. } => {} 311 e => panic!("unexpected error: {:?}", e), 312 } 313 314 // cannot release when unexpired (height == end_height) 315 let msg = ExecuteMsg::Refund {}; 316 let mut env = mock_env(); 317 env.block.height = 1000; 318 env.block.time = Timestamp::from_seconds(0); 319 let info = mock_info("anybody", &[]); 320 let execute_res = execute(deps.as_mut(), env, info, msg.clone()); 321 match execute_res.unwrap_err() { 322 ContractError::NotExpired { .. } => {} 323 e => panic!("unexpected error: {:?}", e), 324 } 325 326 // anyone can release after expiration 327 let mut env = mock_env(); 328 env.block.height = 1001; 329 env.block.time = Timestamp::from_seconds(0); 330 let info = mock_info("anybody", &[]); 331 let execute_res = execute(deps.as_mut(), env, info, msg.clone()).unwrap(); 332 assert_eq!(1, execute_res.messages.len()); 333 let msg = execute_res.messages.get(0).expect("no message"); 334 assert_eq!( 335 msg.msg, 336 CosmosMsg::Bank(BankMsg::Send { 337 to_address: "creator".into(), 338 amount: coins(1000, "earth"), 339 }) 340 ); 341 } 342 }