defuse/contract/tokens/nep171/
withdraw.rs1use crate::{
2 contract::{Contract, ContractExt, Role, tokens::STORAGE_DEPOSIT_GAS},
3 tokens::nep171::{
4 NonFungibleTokenForceWithdrawer, NonFungibleTokenWithdrawResolver,
5 NonFungibleTokenWithdrawer,
6 },
7};
8use defuse_core::{
9 DefuseError, Result,
10 engine::StateView,
11 intents::tokens::NftWithdraw,
12 token_id::{nep141::Nep141TokenId, nep171::Nep171TokenId},
13};
14use defuse_near_utils::{
15 REFUND_MEMO, UnwrapOrPanic, promise_result_checked_json, promise_result_checked_void,
16};
17
18use defuse_wnear::{NEAR_WITHDRAW_GAS, ext_wnear};
19use near_contract_standards::{
20 non_fungible_token::{self, core::ext_nft_core},
21 storage_management::ext_storage_management,
22};
23use near_plugins::{AccessControllable, Pausable, access_control_any, pause};
24use near_sdk::{
25 AccountId, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env, json_types::U128,
26 near, require,
27};
28use std::iter;
29
30#[near]
31impl NonFungibleTokenWithdrawer for Contract {
32 #[pause]
33 #[payable]
34 fn nft_withdraw(
35 &mut self,
36 token: AccountId,
37 receiver_id: AccountId,
38 token_id: non_fungible_token::TokenId,
39 memo: Option<String>,
40 msg: Option<String>,
41 ) -> PromiseOrValue<bool> {
42 assert_one_yocto();
43 self.internal_nft_withdraw(
44 self.ensure_auth_predecessor_id(),
45 NftWithdraw {
46 token,
47 receiver_id,
48 token_id,
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_nft_withdraw(
62 &mut self,
63 owner_id: AccountId,
64 withdraw: NftWithdraw,
65 force: bool,
66 ) -> Result<PromiseOrValue<bool>> {
67 self.withdraw(
68 &owner_id,
69 iter::once((
70 Nep171TokenId::new(withdraw.token.clone(), withdraw.token_id.clone()).into(),
71 1,
72 ))
73 .chain(withdraw.storage_deposit.map(|amount| {
74 (
75 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
76 amount.as_yoctonear(),
77 )
78 })),
79 Some("withdraw"),
80 force,
81 )?;
82
83 let is_call = withdraw.is_call();
84 Ok(if let Some(storage_deposit) = withdraw.storage_deposit {
85 ext_wnear::ext(self.wnear_id.clone())
86 .with_attached_deposit(NearToken::from_yoctonear(1))
87 .with_static_gas(NEAR_WITHDRAW_GAS)
88 .with_unused_gas_weight(0)
90 .near_withdraw(U128(storage_deposit.as_yoctonear()))
91 .then(
92 Self::ext(env::current_account_id())
94 .with_static_gas(
95 Self::DO_NFT_WITHDRAW_GAS
96 .checked_add(withdraw.min_gas())
97 .ok_or(DefuseError::GasOverflow)
98 .unwrap_or_panic(),
99 )
100 .do_nft_withdraw(withdraw.clone()),
101 )
102 } else {
103 Self::do_nft_withdraw(withdraw.clone())
104 }
105 .then(
106 Self::ext(env::current_account_id())
107 .with_static_gas(Self::NFT_RESOLVE_WITHDRAW_GAS)
108 .with_unused_gas_weight(0)
110 .nft_resolve_withdraw(withdraw.token, owner_id, withdraw.token_id, is_call),
111 )
112 .into())
113 }
114}
115
116#[near]
117impl Contract {
118 const NFT_RESOLVE_WITHDRAW_GAS: Gas = Gas::from_tgas(5);
119 const DO_NFT_WITHDRAW_GAS: Gas = Gas::from_tgas(5)
120 .saturating_add(STORAGE_DEPOSIT_GAS);
123
124 #[private]
125 pub fn do_nft_withdraw(withdraw: NftWithdraw) -> Promise {
126 let min_gas = withdraw.min_gas();
127 let p = if let Some(storage_deposit) = withdraw.storage_deposit {
128 require!(
129 promise_result_checked_void(0).is_ok(),
130 "near_withdraw failed",
131 );
132
133 ext_storage_management::ext(withdraw.token)
134 .with_attached_deposit(storage_deposit)
135 .with_static_gas(STORAGE_DEPOSIT_GAS)
136 .with_unused_gas_weight(0)
138 .storage_deposit(Some(withdraw.receiver_id.clone()), None)
139 } else {
140 Promise::new(withdraw.token)
141 };
142
143 let p = ext_nft_core::ext_on(p)
144 .with_attached_deposit(NearToken::from_yoctonear(1))
145 .with_static_gas(min_gas)
146 .with_unused_gas_weight(1);
148 if let Some(msg) = withdraw.msg {
149 p.nft_transfer_call(
150 withdraw.receiver_id,
151 withdraw.token_id,
152 None,
153 withdraw.memo,
154 msg,
155 )
156 } else {
157 p.nft_transfer(withdraw.receiver_id, withdraw.token_id, None, withdraw.memo)
158 }
159 }
160}
161
162#[near]
163impl NonFungibleTokenWithdrawResolver for Contract {
164 #[private]
165 fn nft_resolve_withdraw(
166 &mut self,
167 token: AccountId,
168 sender_id: AccountId,
169 token_id: non_fungible_token::TokenId,
170 is_call: bool,
171 ) -> bool {
172 let used = if is_call {
173 match promise_result_checked_json::<bool>(0) {
175 Ok(Ok(used)) => used,
176 Ok(Err(_deserialization_err)) => false,
177 Err(_) => true,
181 }
182 } else {
183 promise_result_checked_void(0).is_ok()
185 };
186
187 if !used {
188 self.deposit(
189 sender_id,
190 [(Nep171TokenId::new(token, token_id).into(), 1)],
191 Some(REFUND_MEMO),
192 )
193 .unwrap_or_panic();
194 }
195
196 used
197 }
198}
199
200#[near]
201impl NonFungibleTokenForceWithdrawer for Contract {
202 #[access_control_any(roles(Role::DAO, Role::UnrestrictedWithdrawer))]
203 #[payable]
204 fn nft_force_withdraw(
205 &mut self,
206 owner_id: AccountId,
207 token: AccountId,
208 receiver_id: AccountId,
209 token_id: non_fungible_token::TokenId,
210 memo: Option<String>,
211 msg: Option<String>,
212 ) -> PromiseOrValue<bool> {
213 assert_one_yocto();
214 self.internal_nft_withdraw(
215 owner_id,
216 NftWithdraw {
217 token,
218 receiver_id,
219 token_id,
220 memo,
221 msg,
222 storage_deposit: None,
223 min_gas: None,
224 },
225 true,
226 )
227 .unwrap_or_panic()
228 }
229}