defuse/contract/tokens/nep141/
withdraw.rs1use crate::{
2 contract::{Contract, ContractExt, Role, tokens::STORAGE_DEPOSIT_GAS},
3 tokens::nep141::{
4 FungibleTokenForceWithdrawer, FungibleTokenWithdrawResolver, FungibleTokenWithdrawer,
5 },
6};
7use core::iter;
8use defuse_core::{
9 DefuseError, Result, engine::StateView, intents::tokens::FtWithdraw,
10 token_id::nep141::Nep141TokenId,
11};
12use defuse_near_utils::{REFUND_MEMO, promise_result_checked_json, promise_result_checked_void};
13use defuse_wnear::{NEAR_WITHDRAW_GAS, ext_wnear};
14use near_contract_standards::{
15 fungible_token::core::ext_ft_core, storage_management::ext_storage_management,
16};
17use near_plugins::{AccessControllable, Pausable, access_control_any, pause};
18use near_sdk::{
19 AccountId, FunctionError, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env,
20 json_types::U128, near, require,
21};
22
23#[near]
24impl FungibleTokenWithdrawer for Contract {
25 #[pause]
26 #[payable]
27 fn ft_withdraw(
28 &mut self,
29 token: AccountId,
30 receiver_id: AccountId,
31 amount: U128,
32 memo: Option<String>,
33 msg: Option<String>,
34 ) -> PromiseOrValue<U128> {
35 assert_one_yocto();
36 self.internal_ft_withdraw(
37 self.ensure_auth_predecessor_id(),
38 FtWithdraw {
39 token,
40 receiver_id,
41 amount: amount.into(),
42 memo,
43 msg,
44 storage_deposit: None,
45 min_gas: None,
46 },
47 false,
48 )
49 .unwrap_or_else(|err| err.panic())
50 }
51}
52
53impl Contract {
54 pub(crate) fn internal_ft_withdraw(
55 &mut self,
56 owner_id: AccountId,
57 withdraw: FtWithdraw,
58 force: bool,
59 ) -> Result<PromiseOrValue<U128>> {
60 self.withdraw(
61 &owner_id,
62 iter::once((
63 Nep141TokenId::new(withdraw.token.clone()).into(),
64 withdraw.amount,
65 ))
66 .chain(withdraw.storage_deposit.map(|amount| {
67 (
68 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
69 amount.as_yoctonear(),
70 )
71 })),
72 Some("withdraw"),
73 force,
74 )?;
75
76 let is_call = withdraw.is_call();
77 Ok(if let Some(storage_deposit) = withdraw.storage_deposit {
78 ext_wnear::ext(self.wnear_id.clone())
79 .with_attached_deposit(NearToken::from_yoctonear(1))
80 .with_static_gas(NEAR_WITHDRAW_GAS)
81 .with_unused_gas_weight(0)
83 .near_withdraw(U128(storage_deposit.as_yoctonear()))
84 .then(
85 Self::ext(env::current_account_id())
87 .with_static_gas(
88 Self::DO_FT_WITHDRAW_GAS
89 .checked_add(withdraw.min_gas())
90 .ok_or(DefuseError::GasOverflow)
91 .unwrap_or_else(|err| err.panic()),
92 )
93 .do_ft_withdraw(withdraw.clone()),
94 )
95 } else {
96 Self::do_ft_withdraw(withdraw.clone())
97 }
98 .then(
99 Self::ext(env::current_account_id())
100 .with_static_gas(Self::FT_RESOLVE_WITHDRAW_GAS)
101 .with_unused_gas_weight(0)
103 .ft_resolve_withdraw(withdraw.token, owner_id, withdraw.amount.into(), is_call),
104 )
105 .into())
106 }
107}
108
109#[near]
110impl Contract {
111 const FT_RESOLVE_WITHDRAW_GAS: Gas = Gas::from_tgas(5);
112 const DO_FT_WITHDRAW_GAS: Gas = Gas::from_tgas(5)
113 .saturating_add(STORAGE_DEPOSIT_GAS);
116
117 #[private]
118 pub fn do_ft_withdraw(withdraw: FtWithdraw) -> Promise {
119 let min_gas = withdraw.min_gas();
120 let p = if let Some(storage_deposit) = withdraw.storage_deposit {
121 require!(
122 promise_result_checked_void(0).is_ok(),
123 "near_withdraw failed",
124 );
125
126 ext_storage_management::ext(withdraw.token)
127 .with_attached_deposit(storage_deposit)
128 .with_static_gas(STORAGE_DEPOSIT_GAS)
129 .with_unused_gas_weight(0)
131 .storage_deposit(Some(withdraw.receiver_id.clone()), None)
132 } else {
133 Promise::new(withdraw.token)
134 };
135
136 let p = ext_ft_core::ext_on(p)
137 .with_attached_deposit(NearToken::from_yoctonear(1))
138 .with_static_gas(min_gas)
139 .with_unused_gas_weight(1);
141 if let Some(msg) = withdraw.msg {
142 p.ft_transfer_call(
143 withdraw.receiver_id,
144 withdraw.amount.into(),
145 withdraw.memo,
146 msg,
147 )
148 } else {
149 p.ft_transfer(withdraw.receiver_id, withdraw.amount.into(), withdraw.memo)
150 }
151 }
152}
153
154#[near]
155impl FungibleTokenWithdrawResolver for Contract {
156 #[private]
157 fn ft_resolve_withdraw(
158 &mut self,
159 token: AccountId,
160 sender_id: AccountId,
161 amount: U128,
162 is_call: bool,
163 ) -> U128 {
164 let used = if is_call {
165 match promise_result_checked_json::<U128>(0) {
167 Ok(Ok(used)) => used.0.min(amount.0),
168 Ok(Err(_deserialize_err)) => 0,
169 Err(_) => amount.0,
173 }
174 } else {
175 if promise_result_checked_void(0).is_ok() {
177 amount.0
178 } else {
179 0
180 }
181 };
182
183 let refund = amount.0.saturating_sub(used);
184 if refund > 0 {
185 self.deposit(
186 sender_id,
187 [(Nep141TokenId::new(token).into(), refund)],
188 Some(REFUND_MEMO),
189 )
190 .unwrap_or_else(|err| err.panic());
191 }
192
193 U128(used)
194 }
195}
196
197#[near]
198impl FungibleTokenForceWithdrawer for Contract {
199 #[access_control_any(roles(Role::DAO, Role::UnrestrictedWithdrawer))]
200 #[payable]
201 fn ft_force_withdraw(
202 &mut self,
203 owner_id: AccountId,
204 token: AccountId,
205 receiver_id: AccountId,
206 amount: U128,
207 memo: Option<String>,
208 msg: Option<String>,
209 ) -> PromiseOrValue<U128> {
210 assert_one_yocto();
211 self.internal_ft_withdraw(
212 owner_id,
213 FtWithdraw {
214 token,
215 receiver_id,
216 amount: amount.into(),
217 memo,
218 msg,
219 storage_deposit: None,
220 min_gas: None,
221 },
222 true,
223 )
224 .unwrap_or_else(|err| err.panic())
225 }
226}