defuse/contract/tokens/nep245/
withdraw.rs1#![allow(clippy::too_many_arguments)]
2
3use crate::{
4 contract::{Contract, ContractExt, Role, tokens::STORAGE_DEPOSIT_GAS},
5 tokens::nep245::{
6 MultiTokenForcedWithdrawer, MultiTokenWithdrawResolver, MultiTokenWithdrawer,
7 },
8};
9use defuse_core::{
10 DefuseError, Result,
11 engine::StateView,
12 intents::tokens::MtWithdraw,
13 token_id::{nep141::Nep141TokenId, nep245::Nep245TokenId},
14};
15use defuse_near_utils::{
16 REFUND_MEMO, UnwrapOrPanic, UnwrapOrPanicError, promise_result_checked_json_with_len,
17 promise_result_checked_void,
18};
19use defuse_nep245::ext_mt_core;
20use defuse_wnear::{NEAR_WITHDRAW_GAS, ext_wnear};
21use near_contract_standards::storage_management::ext_storage_management;
22use near_plugins::{AccessControllable, Pausable, access_control_any, pause};
23use near_sdk::{
24 AccountId, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env, json_types::U128,
25 near, require,
26};
27
28#[near]
29impl MultiTokenWithdrawer for Contract {
30 #[pause]
31 #[payable]
32 fn mt_withdraw(
33 &mut self,
34 token: AccountId,
35 receiver_id: AccountId,
36 token_ids: Vec<defuse_nep245::TokenId>,
37 amounts: Vec<U128>,
38 memo: Option<String>,
39 msg: Option<String>,
40 ) -> PromiseOrValue<Vec<U128>> {
41 assert_one_yocto();
42 self.internal_mt_withdraw(
43 self.ensure_auth_predecessor_id(),
44 MtWithdraw {
45 token,
46 receiver_id,
47 token_ids,
48 amounts,
49 memo,
50 msg,
51 storage_deposit: None,
52 min_gas: None,
53 },
54 false,
55 )
56 .unwrap_or_panic()
57 }
58}
59
60impl Contract {
61 pub(crate) fn internal_mt_withdraw(
62 &mut self,
63 owner_id: AccountId,
64 withdraw: MtWithdraw,
65 force: bool,
66 ) -> Result<PromiseOrValue<Vec<U128>>> {
67 if withdraw.token_ids.len() != withdraw.amounts.len() || withdraw.token_ids.is_empty() {
68 return Err(DefuseError::InvalidIntent);
69 }
70
71 self.withdraw(
72 &owner_id,
73 withdraw
74 .token_ids
75 .iter()
76 .cloned()
77 .map(|token_id| Nep245TokenId::new(withdraw.token.clone(), token_id))
78 .map(Into::into)
79 .zip(withdraw.amounts.iter().map(|a| a.0))
80 .chain(withdraw.storage_deposit.map(|amount| {
81 (
82 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
83 amount.as_yoctonear(),
84 )
85 })),
86 Some("withdraw"),
87 force,
88 )?;
89
90 let is_call = withdraw.msg.is_some();
91 Ok(if let Some(storage_deposit) = withdraw.storage_deposit {
92 ext_wnear::ext(self.wnear_id.clone())
93 .with_attached_deposit(NearToken::from_yoctonear(1))
94 .with_static_gas(NEAR_WITHDRAW_GAS)
95 .with_unused_gas_weight(0)
97 .near_withdraw(U128(storage_deposit.as_yoctonear()))
98 .then(
99 Self::ext(env::current_account_id())
101 .with_static_gas(
102 Self::DO_MT_WITHDRAW_GAS
103 .checked_add(withdraw.min_gas())
104 .ok_or(DefuseError::GasOverflow)
105 .unwrap_or_panic(),
106 )
107 .do_mt_withdraw(withdraw.clone()),
108 )
109 } else {
110 Self::do_mt_withdraw(withdraw.clone())
111 }
112 .then(
113 Self::ext(env::current_account_id())
114 .with_static_gas(Self::mt_resolve_withdraw_gas(withdraw.token_ids.len()))
115 .with_unused_gas_weight(0)
117 .mt_resolve_withdraw(
118 withdraw.token,
119 owner_id,
120 withdraw.token_ids,
121 withdraw.amounts,
122 is_call,
123 ),
124 )
125 .into())
126 }
127
128 #[must_use]
129 fn mt_resolve_withdraw_gas(token_count: usize) -> Gas {
130 const MT_RESOLVE_WITHDRAW_PER_TOKEN_GAS: Gas = Gas::from_tgas(2);
132 const MT_RESOLVE_WITHDRAW_BASE_GAS: Gas = Gas::from_tgas(8);
133
134 let token_count: u64 = token_count.try_into().unwrap_or_panic_display();
135
136 MT_RESOLVE_WITHDRAW_BASE_GAS
137 .checked_add(
138 MT_RESOLVE_WITHDRAW_PER_TOKEN_GAS
139 .checked_mul(token_count)
140 .unwrap_or_panic(),
141 )
142 .unwrap_or_panic()
143 }
144}
145
146#[near]
147impl Contract {
148 const DO_MT_WITHDRAW_GAS: Gas = Gas::from_tgas(5)
149 .saturating_add(STORAGE_DEPOSIT_GAS);
152
153 #[private]
154 pub fn do_mt_withdraw(withdraw: MtWithdraw) -> Promise {
155 let min_gas = withdraw.min_gas();
156 let p = if let Some(storage_deposit) = withdraw.storage_deposit {
157 require!(
158 promise_result_checked_void(0).is_ok(),
159 "near_withdraw failed",
160 );
161
162 ext_storage_management::ext(withdraw.token)
163 .with_attached_deposit(storage_deposit)
164 .with_static_gas(STORAGE_DEPOSIT_GAS)
165 .with_unused_gas_weight(0)
167 .storage_deposit(Some(withdraw.receiver_id.clone()), None)
168 } else {
169 Promise::new(withdraw.token)
170 };
171
172 let p = ext_mt_core::ext_on(p)
173 .with_attached_deposit(NearToken::from_yoctonear(1))
174 .with_static_gas(min_gas)
175 .with_unused_gas_weight(1);
177 if let Some(msg) = withdraw.msg {
178 p.mt_batch_transfer_call(
179 withdraw.receiver_id,
180 withdraw.token_ids,
181 withdraw.amounts,
182 None,
183 withdraw.memo,
184 msg,
185 )
186 } else {
187 p.mt_batch_transfer(
188 withdraw.receiver_id,
189 withdraw.token_ids,
190 withdraw.amounts,
191 None,
192 withdraw.memo,
193 )
194 }
195 }
196}
197
198#[near]
199impl MultiTokenWithdrawResolver for Contract {
200 #[private]
201 fn mt_resolve_withdraw(
202 &mut self,
203 token: AccountId,
204 sender_id: AccountId,
205 token_ids: Vec<defuse_nep245::TokenId>,
206 amounts: Vec<U128>,
207 is_call: bool,
208 ) -> Vec<U128> {
209 require!(
210 token_ids.len() == amounts.len() && !amounts.is_empty(),
211 "invalid args"
212 );
213
214 let mut used = if is_call {
215 match promise_result_checked_json_with_len::<Vec<U128>>(0, amounts.len()) {
217 Ok(Ok(used)) if used.len() == amounts.len() => used,
218 Ok(_) => vec![U128(0); amounts.len()],
219 Err(_) => amounts.clone(),
223 }
224 } else {
225 if promise_result_checked_void(0).is_ok() {
227 amounts.clone()
228 } else {
229 vec![U128(0); amounts.len()]
230 }
231 };
232
233 self.deposit(
234 sender_id,
235 token_ids
236 .into_iter()
237 .zip(amounts)
238 .zip(&mut used)
239 .filter_map(|((token_id, amount), used)| {
240 used.0 = used.0.min(amount.0);
242 let refund = amount.0.saturating_sub(used.0);
243 if refund > 0 {
244 Some((Nep245TokenId::new(token.clone(), token_id).into(), refund))
245 } else {
246 None
247 }
248 }),
249 Some(REFUND_MEMO),
250 )
251 .unwrap_or_panic();
252
253 used
254 }
255}
256
257#[near]
258impl MultiTokenForcedWithdrawer for Contract {
259 #[access_control_any(roles(Role::DAO, Role::UnrestrictedWithdrawer))]
260 #[payable]
261 fn mt_force_withdraw(
262 &mut self,
263 owner_id: AccountId,
264 token: AccountId,
265 receiver_id: AccountId,
266 token_ids: Vec<defuse_nep245::TokenId>,
267 amounts: Vec<U128>,
268 memo: Option<String>,
269 msg: Option<String>,
270 ) -> PromiseOrValue<Vec<U128>> {
271 assert_one_yocto();
272 self.internal_mt_withdraw(
273 owner_id,
274 MtWithdraw {
275 token,
276 receiver_id,
277 token_ids,
278 amounts,
279 memo,
280 msg,
281 storage_deposit: None,
282 min_gas: None,
283 },
284 true,
285 )
286 .unwrap_or_panic()
287 }
288}