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