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::{REFUND_MEMO, promise_result_checked_json, promise_result_checked_void};
15use defuse_wnear::{NEAR_WITHDRAW_GAS, ext_wnear};
16use near_contract_standards::{
17 non_fungible_token::{self, core::ext_nft_core},
18 storage_management::ext_storage_management,
19};
20use near_plugins::{AccessControllable, Pausable, access_control_any, pause};
21use near_sdk::{
22 AccountId, FunctionError, Gas, NearToken, Promise, PromiseOrValue, assert_one_yocto, env,
23 json_types::U128, near, require,
24};
25use std::iter;
26
27#[near]
28impl NonFungibleTokenWithdrawer for Contract {
29 #[pause]
30 #[payable]
31 fn nft_withdraw(
32 &mut self,
33 token: AccountId,
34 receiver_id: AccountId,
35 token_id: non_fungible_token::TokenId,
36 memo: Option<String>,
37 msg: Option<String>,
38 ) -> PromiseOrValue<bool> {
39 assert_one_yocto();
40 self.internal_nft_withdraw(
41 self.ensure_auth_predecessor_id(),
42 NftWithdraw {
43 token,
44 receiver_id,
45 token_id,
46 memo,
47 msg,
48 storage_deposit: None,
49 min_gas: None,
50 },
51 false,
52 )
53 .unwrap_or_else(|err| err.panic())
54 }
55}
56
57impl Contract {
58 pub(crate) fn internal_nft_withdraw(
59 &mut self,
60 owner_id: AccountId,
61 withdraw: NftWithdraw,
62 force: bool,
63 ) -> Result<PromiseOrValue<bool>> {
64 self.withdraw(
65 &owner_id,
66 iter::once((
67 Nep171TokenId::new(withdraw.token.clone(), withdraw.token_id.clone()).into(),
68 1,
69 ))
70 .chain(withdraw.storage_deposit.map(|amount| {
71 (
72 Nep141TokenId::new(self.wnear_id().into_owned()).into(),
73 amount.as_yoctonear(),
74 )
75 })),
76 Some("withdraw"),
77 force,
78 )?;
79
80 let is_call = withdraw.is_call();
81 Ok(if let Some(storage_deposit) = withdraw.storage_deposit {
82 ext_wnear::ext(self.wnear_id.clone())
83 .with_attached_deposit(NearToken::from_yoctonear(1))
84 .with_static_gas(NEAR_WITHDRAW_GAS)
85 .with_unused_gas_weight(0)
87 .near_withdraw(U128(storage_deposit.as_yoctonear()))
88 .then(
89 Self::ext(env::current_account_id())
91 .with_static_gas(
92 Self::DO_NFT_WITHDRAW_GAS
93 .checked_add(withdraw.min_gas())
94 .ok_or(DefuseError::GasOverflow)
95 .unwrap_or_else(|err| err.panic()),
96 )
97 .do_nft_withdraw(withdraw.clone()),
98 )
99 } else {
100 Self::do_nft_withdraw(withdraw.clone())
101 }
102 .then(
103 Self::ext(env::current_account_id())
104 .with_static_gas(Self::NFT_RESOLVE_WITHDRAW_GAS)
105 .with_unused_gas_weight(0)
107 .nft_resolve_withdraw(withdraw.token, owner_id, withdraw.token_id, is_call),
108 )
109 .into())
110 }
111}
112
113#[near]
114impl Contract {
115 const NFT_RESOLVE_WITHDRAW_GAS: Gas = Gas::from_tgas(5);
116 const DO_NFT_WITHDRAW_GAS: Gas = Gas::from_tgas(5)
117 .saturating_add(STORAGE_DEPOSIT_GAS);
120
121 #[private]
122 pub fn do_nft_withdraw(withdraw: NftWithdraw) -> Promise {
123 let min_gas = withdraw.min_gas();
124 let p = if let Some(storage_deposit) = withdraw.storage_deposit {
125 require!(
126 promise_result_checked_void(0).is_ok(),
127 "near_withdraw failed",
128 );
129
130 ext_storage_management::ext(withdraw.token)
131 .with_attached_deposit(storage_deposit)
132 .with_static_gas(STORAGE_DEPOSIT_GAS)
133 .with_unused_gas_weight(0)
135 .storage_deposit(Some(withdraw.receiver_id.clone()), None)
136 } else {
137 Promise::new(withdraw.token)
138 };
139
140 let p = ext_nft_core::ext_on(p)
141 .with_attached_deposit(NearToken::from_yoctonear(1))
142 .with_static_gas(min_gas)
143 .with_unused_gas_weight(1);
145 if let Some(msg) = withdraw.msg {
146 p.nft_transfer_call(
147 withdraw.receiver_id,
148 withdraw.token_id,
149 None,
150 withdraw.memo,
151 msg,
152 )
153 } else {
154 p.nft_transfer(withdraw.receiver_id, withdraw.token_id, None, withdraw.memo)
155 }
156 }
157}
158
159#[near]
160impl NonFungibleTokenWithdrawResolver for Contract {
161 #[private]
162 fn nft_resolve_withdraw(
163 &mut self,
164 token: AccountId,
165 sender_id: AccountId,
166 token_id: non_fungible_token::TokenId,
167 is_call: bool,
168 ) -> bool {
169 let used = if is_call {
170 match promise_result_checked_json::<bool>(0) {
172 Ok(Ok(used)) => used,
173 Ok(Err(_deserialization_err)) => false,
174 Err(_) => true,
178 }
179 } else {
180 promise_result_checked_void(0).is_ok()
182 };
183
184 if !used {
185 self.deposit(
186 sender_id,
187 [(Nep171TokenId::new(token, token_id).into(), 1)],
188 Some(REFUND_MEMO),
189 )
190 .unwrap_or_else(|err| err.panic());
191 }
192
193 used
194 }
195}
196
197#[near]
198impl NonFungibleTokenForceWithdrawer for Contract {
199 #[access_control_any(roles(Role::DAO, Role::UnrestrictedWithdrawer))]
200 #[payable]
201 fn nft_force_withdraw(
202 &mut self,
203 owner_id: AccountId,
204 token: AccountId,
205 receiver_id: AccountId,
206 token_id: non_fungible_token::TokenId,
207 memo: Option<String>,
208 msg: Option<String>,
209 ) -> PromiseOrValue<bool> {
210 assert_one_yocto();
211 self.internal_nft_withdraw(
212 owner_id,
213 NftWithdraw {
214 token,
215 receiver_id,
216 token_id,
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}