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