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 tokens::BATCH_MAX_TOKENS,
15};
16use defuse_near_utils::{
17 REFUND_MEMO, promise_result_checked_json_with_len, 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, FunctionError, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env,
25 json_types::U128, 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
43 self.internal_mt_withdraw(
44 self.ensure_auth_predecessor_id(),
45 MtWithdraw {
46 token,
47 receiver_id,
48 token_ids,
49 amounts: amounts.into_iter().map(Into::into).collect(),
50 memo,
51 msg,
52 storage_deposit: None,
53 min_gas: None,
54 },
55 false,
56 )
57 .unwrap_or_else(|err| err.panic())
58 }
59}
60
61impl Contract {
62 pub(crate) fn internal_mt_withdraw(
63 &mut self,
64 owner_id: AccountId,
65 withdraw: MtWithdraw,
66 force: bool,
67 ) -> Result<PromiseOrValue<Vec<U128>>> {
68 if withdraw.token_ids.len() != withdraw.amounts.len() || withdraw.token_ids.is_empty() {
69 return Err(DefuseError::InvalidIntent);
70 }
71
72 if withdraw.token_ids.len() > BATCH_MAX_TOKENS {
73 return Err(DefuseError::TooManyTokens(withdraw.token_ids.len()));
74 }
75
76 self.withdraw(
77 &owner_id,
78 withdraw
79 .token_ids
80 .iter()
81 .cloned()
82 .map(|token_id| Nep245TokenId::new(withdraw.token.clone(), token_id))
83 .map(Into::into)
84 .zip(withdraw.amounts.iter().copied())
85 .chain(withdraw.storage_deposit.map(|amount| {
86 (
87 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
88 amount.as_yoctonear(),
89 )
90 })),
91 Some("withdraw"),
92 force,
93 )?;
94
95 let is_call = withdraw.msg.is_some();
96 Ok(if let Some(storage_deposit) = withdraw.storage_deposit {
97 ext_wnear::ext(self.wnear_id.clone())
98 .with_attached_deposit(NearToken::from_yoctonear(1))
99 .with_static_gas(NEAR_WITHDRAW_GAS)
100 .with_unused_gas_weight(0)
102 .near_withdraw(U128(storage_deposit.as_yoctonear()))
103 .then(
104 Self::ext(env::current_account_id())
106 .with_static_gas(
107 Self::DO_MT_WITHDRAW_GAS
108 .checked_add(withdraw.min_gas())
109 .ok_or(DefuseError::GasOverflow)
110 .unwrap_or_else(|err| err.panic()),
111 )
112 .do_mt_withdraw(withdraw.clone()),
113 )
114 } else {
115 Self::do_mt_withdraw(withdraw.clone())
116 }
117 .then(
118 Self::ext(env::current_account_id())
119 .with_static_gas(Self::mt_resolve_withdraw_gas(withdraw.token_ids.len()))
120 .with_unused_gas_weight(0)
122 .mt_resolve_withdraw(
123 withdraw.token,
124 owner_id,
125 withdraw.token_ids,
126 withdraw.amounts.into_iter().map(U128).collect(),
127 is_call,
128 ),
129 )
130 .into())
131 }
132
133 #[must_use]
134 fn mt_resolve_withdraw_gas(token_count: usize) -> Gas {
135 const MT_RESOLVE_WITHDRAW_PER_TOKEN_GAS: Gas = Gas::from_tgas(5);
137 const MT_RESOLVE_WITHDRAW_BASE_GAS: Gas = Gas::from_tgas(10);
138
139 let token_count: u64 = token_count.try_into().unwrap();
140
141 MT_RESOLVE_WITHDRAW_BASE_GAS
142 .checked_add(
143 MT_RESOLVE_WITHDRAW_PER_TOKEN_GAS
144 .checked_mul(token_count)
145 .ok_or(DefuseError::GasOverflow)
146 .unwrap_or_else(|err| err.panic()),
147 )
148 .ok_or(DefuseError::GasOverflow)
149 .unwrap_or_else(|err| err.panic())
150 }
151}
152
153#[near]
154impl Contract {
155 const DO_MT_WITHDRAW_GAS: Gas = Gas::from_tgas(5)
156 .saturating_add(STORAGE_DEPOSIT_GAS);
159
160 #[private]
161 pub fn do_mt_withdraw(withdraw: MtWithdraw) -> Promise {
162 let min_gas = withdraw.min_gas();
163 let p = if let Some(storage_deposit) = withdraw.storage_deposit {
164 require!(
165 promise_result_checked_void(0).is_ok(),
166 "near_withdraw failed",
167 );
168
169 ext_storage_management::ext(withdraw.token)
170 .with_attached_deposit(storage_deposit)
171 .with_static_gas(STORAGE_DEPOSIT_GAS)
172 .with_unused_gas_weight(0)
174 .storage_deposit(Some(withdraw.receiver_id.clone()), None)
175 } else {
176 Promise::new(withdraw.token)
177 };
178
179 let p = ext_mt_core::ext_on(p)
180 .with_attached_deposit(NearToken::from_yoctonear(1))
181 .with_static_gas(min_gas)
182 .with_unused_gas_weight(1);
184 let amounts: Vec<U128> = withdraw.amounts.into_iter().map(U128).collect();
185 if let Some(msg) = withdraw.msg {
186 p.mt_batch_transfer_call(
187 withdraw.receiver_id,
188 withdraw.token_ids,
189 amounts,
190 None,
191 withdraw.memo,
192 msg,
193 )
194 } else {
195 p.mt_batch_transfer(
196 withdraw.receiver_id,
197 withdraw.token_ids,
198 amounts,
199 None,
200 withdraw.memo,
201 )
202 }
203 }
204}
205
206#[near]
207impl MultiTokenWithdrawResolver for Contract {
208 #[private]
209 fn mt_resolve_withdraw(
210 &mut self,
211 token: AccountId,
212 sender_id: AccountId,
213 token_ids: Vec<defuse_nep245::TokenId>,
214 amounts: Vec<U128>,
215 is_call: bool,
216 ) -> Vec<U128> {
217 require!(
218 token_ids.len() == amounts.len() && !amounts.is_empty(),
219 "invalid args"
220 );
221
222 let mut used = if is_call {
223 match promise_result_checked_json_with_len::<Vec<U128>>(0, amounts.len()) {
225 Ok(Ok(used)) if used.len() == amounts.len() => used,
226 Ok(_) => vec![U128(0); amounts.len()],
227 Err(_) => amounts.clone(),
231 }
232 } else {
233 if promise_result_checked_void(0).is_ok() {
235 amounts.clone()
236 } else {
237 vec![U128(0); amounts.len()]
238 }
239 };
240
241 self.deposit(
242 sender_id,
243 token_ids
244 .into_iter()
245 .zip(amounts)
246 .zip(&mut used)
247 .filter_map(|((token_id, amount), used)| {
248 used.0 = used.0.min(amount.0);
250 let refund = amount.0.saturating_sub(used.0);
251 if refund > 0 {
252 Some((Nep245TokenId::new(token.clone(), token_id).into(), refund))
253 } else {
254 None
255 }
256 }),
257 Some(REFUND_MEMO),
258 )
259 .unwrap_or_else(|err| err.panic());
260
261 used
262 }
263}
264
265#[near]
266impl MultiTokenForcedWithdrawer for Contract {
267 #[access_control_any(roles(Role::DAO, Role::UnrestrictedWithdrawer))]
268 #[payable]
269 fn mt_force_withdraw(
270 &mut self,
271 owner_id: AccountId,
272 token: AccountId,
273 receiver_id: AccountId,
274 token_ids: Vec<defuse_nep245::TokenId>,
275 amounts: Vec<U128>,
276 memo: Option<String>,
277 msg: Option<String>,
278 ) -> PromiseOrValue<Vec<U128>> {
279 assert_one_yocto();
280
281 self.internal_mt_withdraw(
282 owner_id,
283 MtWithdraw {
284 token,
285 receiver_id,
286 token_ids,
287 amounts: amounts.into_iter().map(Into::into).collect(),
288 memo,
289 msg,
290 storage_deposit: None,
291 min_gas: None,
292 },
293 true,
294 )
295 .unwrap_or_else(|err| err.panic())
296 }
297}