1use std::collections::HashMap;
2
3use near_sdk::{env, near, require, AccountId, Promise, PromiseOrValue};
4use templar_common::{
5 accumulator::Accumulator,
6 asset::{BorrowAsset, BorrowAssetAmount, CollateralAssetAmount},
7 borrow::{BorrowPosition, BorrowStatus},
8 contract::list,
9 market::{BorrowAssetMetrics, HarvestYieldMode, MarketConfiguration, MarketExternalInterface},
10 oracle::pyth::OracleResponse,
11 self_ext,
12 snapshot::Snapshot,
13 supply::{SupplyPosition, WithdrawalAttempt},
14 withdrawal_queue::{
15 WithdrawalQueueExecutionResult, WithdrawalQueueStatus, WithdrawalRequestStatus,
16 },
17};
18use templar_primitives::Decimal;
19
20use crate::{Contract, ContractExt};
21
22#[near]
23impl MarketExternalInterface for Contract {
24 fn get_configuration(&self) -> MarketConfiguration {
25 self.configuration.clone()
26 }
27
28 fn get_current_snapshot(&self) -> Snapshot {
29 self.current_snapshot()
30 }
31
32 fn get_finalized_snapshots_len(&self) -> u32 {
33 self.finalized_snapshots.len()
34 }
35
36 fn get_borrow_asset_metrics(&self) -> BorrowAssetMetrics {
37 BorrowAssetMetrics {
38 available: self.get_borrow_asset_available_to_borrow(),
39 deposited_active: self.borrow_asset_deposited_active,
40 deposited_incoming: self
41 .borrow_asset_deposited_incoming
42 .iter()
43 .map(|incoming| (incoming.activate_at_snapshot_index, incoming.amount))
44 .collect(),
45 borrowed: self.borrowed(),
46 paid_to_fees: self.borrow_asset_paid_to_fees,
47 }
48 }
49
50 fn list_borrow_positions(
51 &self,
52 offset: Option<u32>,
53 count: Option<u32>,
54 ) -> HashMap<AccountId, BorrowPosition> {
55 list(self.iter_borrow_positions(), offset, count)
56 }
57
58 fn list_finalized_snapshots(&self, offset: Option<u32>, count: Option<u32>) -> Vec<&Snapshot> {
59 list(&self.finalized_snapshots, offset, count)
60 }
61
62 fn list_supply_positions(
63 &self,
64 offset: Option<u32>,
65 count: Option<u32>,
66 ) -> HashMap<AccountId, SupplyPosition> {
67 list(self.iter_supply_positions(), offset, count)
68 }
69
70 fn get_borrow_position(&self, account_id: AccountId) -> Option<BorrowPosition> {
71 let borrow_position = self.borrow_position_ref(account_id)?;
72 Some(borrow_position.inner().clone())
73 }
74
75 fn get_borrow_position_pending_interest(
76 &self,
77 account_id: AccountId,
78 snapshot_limit: Option<u32>,
79 ) -> Option<BorrowAssetAmount> {
80 let limit = snapshot_limit.unwrap_or(u32::MAX);
81 let position = self.borrow_position_ref(account_id)?;
82 Some(position.calculate_interest(limit).get_amount())
83 }
84
85 fn get_borrow_status(
86 &self,
87 account_id: AccountId,
88 oracle_response: OracleResponse,
89 ) -> Option<BorrowStatus> {
90 let borrow_position = self.borrow_position_ref(account_id)?;
91
92 let price_pair = self
93 .configuration
94 .price_oracle_configuration
95 .create_price_pair(&oracle_response)
96 .unwrap_or_else(|e| templar_common::panic_with_message(&e.to_string()));
97
98 Some(borrow_position.status(&price_pair, env::block_timestamp_ms()))
99 }
100
101 fn borrow(&mut self, amount: BorrowAssetAmount) -> Promise {
102 require!(!amount.is_zero(), "Borrow amount must be greater than zero");
103 let account_id = env::predecessor_account_id();
104 require!(
105 self.borrow_position_ref(account_id.clone()).is_some(),
106 "Borrow position does not exist",
107 );
108
109 self.configuration
110 .price_oracle_configuration
111 .retrieve_price_pair()
112 .then(
113 self_ext!(Self::GAS_BORROW_01_CONSUME_PRICE)
114 .borrow_01_consume_price(account_id, amount),
115 )
116 }
117
118 fn withdraw_collateral(&mut self, amount: CollateralAssetAmount) -> Promise {
119 let account_id = env::predecessor_account_id();
120
121 let snapshot = self.snapshot();
122 let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id.clone())
123 else {
124 templar_common::panic_with_message(
125 "No borrower record. Please deposit collateral first.",
126 );
127 };
128
129 if borrow_position
130 .inner()
131 .get_total_borrow_asset_liability()
132 .is_zero()
133 {
134 let proof = borrow_position.accumulate_interest();
136 borrow_position.record_collateral_asset_withdrawal_initial(proof, amount);
137 drop(borrow_position);
138
139 self.configuration
140 .collateral_asset
141 .transfer(account_id.clone(), amount)
142 .then(
143 self_ext!(Self::GAS_WITHDRAW_COLLATERAL_02_FINALIZE)
144 .withdraw_collateral_02_finalize(account_id, amount),
145 )
146 } else {
147 drop(borrow_position);
148 self.configuration
150 .price_oracle_configuration
151 .retrieve_price_pair()
152 .then(
153 self_ext!(Self::GAS_WITHDRAW_COLLATERAL_01_CONSUME_PRICE)
154 .withdraw_collateral_01_consume_price(account_id, amount),
155 )
156 }
157 }
158
159 fn apply_interest(&mut self, account_id: Option<AccountId>, snapshot_limit: Option<u32>) {
160 let account_id = account_id.unwrap_or_else(env::predecessor_account_id);
161 let snapshot = self.snapshot();
162 if let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id) {
163 borrow_position.accumulate_interest_partial(snapshot_limit.unwrap_or(u32::MAX));
164 }
165 }
166
167 fn get_supply_position(&self, account_id: AccountId) -> Option<SupplyPosition> {
168 let position = self.supply_position_ref(account_id)?;
169 Some(position.inner().clone())
170 }
171
172 fn get_supply_position_pending_yield(
173 &self,
174 account_id: AccountId,
175 snapshot_limit: Option<u32>,
176 ) -> Option<BorrowAssetAmount> {
177 let limit = snapshot_limit.unwrap_or(u32::MAX);
178 let position = self.supply_position_ref(account_id)?;
179 Some(position.calculate_yield(limit).get_amount())
180 }
181
182 fn create_supply_withdrawal_request(&mut self, amount: BorrowAssetAmount) {
185 require!(
186 !amount.is_zero(),
187 "Amount to withdraw must be greater than zero",
188 );
189 let predecessor = env::predecessor_account_id();
190 let Some(supply_position) = self
191 .supply_position_ref(predecessor.clone())
192 .filter(|supply_position| !supply_position.total_deposit().is_zero())
193 else {
194 templar_common::panic_with_message("Supply position does not exist");
195 };
196
197 require!(
201 supply_position.total_deposit() >= amount,
202 "Attempt to withdraw more than current deposit",
203 );
204 require!(
205 self.configuration.supply_withdrawal_range.contains(amount),
206 "Withdrawal amount is outside of allowable range",
207 );
208
209 self.withdrawal_queue.remove(&predecessor);
210 self.withdrawal_queue.insert_or_update(&predecessor, amount);
211 }
212
213 fn cancel_supply_withdrawal_request(&mut self) {
214 self.withdrawal_queue.remove(&env::predecessor_account_id());
215 }
216
217 fn execute_next_supply_withdrawal_request(
218 &mut self,
219 batch_limit: Option<u32>,
220 ) -> PromiseOrValue<WithdrawalQueueExecutionResult> {
221 let batch_limit = batch_limit.unwrap_or(1);
222 let mut batch = Vec::with_capacity(batch_limit.min(self.withdrawal_queue.len()) as usize);
223 let snapshot_proof = self.snapshot();
224 let block_timestamp_ms = env::block_timestamp_ms();
225 let queue_len_start = self.withdrawal_queue.len();
226 let mut depth_cleared = BorrowAssetAmount::zero();
227
228 while let Some((account_id, requested_amount)) = self.withdrawal_queue.peek() {
229 if batch.len() >= batch_limit as usize {
230 break;
231 }
232
233 let withdrawal_attempt = {
234 let Some(mut position_guard) =
235 self.supply_position_guard(snapshot_proof, account_id.clone())
236 else {
237 self.withdrawal_queue.pop();
240 depth_cleared += requested_amount;
241 continue;
242 };
243 let accumulation_proof = position_guard.accumulate_yield();
244 position_guard.record_withdrawal_initial(
245 accumulation_proof,
246 requested_amount,
247 block_timestamp_ms,
248 )
249 };
250
251 match withdrawal_attempt {
252 WithdrawalAttempt::EmptyPosition => {
253 self.withdrawal_queue.pop();
254 depth_cleared += requested_amount;
255 }
256 WithdrawalAttempt::NoLiquidity => {
257 break;
258 }
259 WithdrawalAttempt::Full(withdrawal) => {
260 batch.push(withdrawal);
261 self.withdrawal_queue.pop();
262 depth_cleared += requested_amount;
263 }
264 WithdrawalAttempt::Partial {
265 withdrawal,
266 remaining,
267 } => {
268 batch.push(withdrawal);
269 self.withdrawal_queue.mut_head(|a| *a = remaining);
270 depth_cleared += requested_amount - remaining;
271 break;
272 }
273 }
274 }
275
276 let result = WithdrawalQueueExecutionResult {
277 depth: depth_cleared,
278 length: queue_len_start - self.withdrawal_queue.len(),
279 };
280
281 let Some(transfers) = batch
282 .iter()
283 .map(|resolution| {
284 self.configuration
285 .borrow_asset
286 .transfer(resolution.account_id.clone(), resolution.amount_to_account)
287 })
288 .reduce(|a, b| a.and(b))
289 else {
290 return PromiseOrValue::Value(result);
291 };
292
293 PromiseOrValue::Promise(
294 transfers.then(
295 self_ext!(Self::GAS_EXECUTE_NEXT_SUPPLY_WITHDRAWAL_REQUEST_01_FINALIZE)
296 .execute_next_supply_withdrawal_request_01_finalize(batch, result),
297 ),
298 )
299 }
300
301 fn get_supply_withdrawal_request_status(
302 &self,
303 account_id: AccountId,
304 ) -> Option<WithdrawalRequestStatus> {
305 self.withdrawal_queue.get_request_status(&account_id)
306 }
307
308 fn get_supply_withdrawal_queue_status(&self) -> WithdrawalQueueStatus {
309 self.withdrawal_queue.get_status()
310 }
311
312 fn harvest_yield(
313 &mut self,
314 account_id: Option<AccountId>,
315 mode: Option<HarvestYieldMode>,
316 ) -> BorrowAssetAmount {
317 let mode = mode.unwrap_or_default();
318 let predecessor = env::predecessor_account_id();
319 let account_id = account_id.unwrap_or_else(|| predecessor.clone());
320
321 let snapshot = self.snapshot();
322 let Some(mut supply_position) = self.supply_position_guard(snapshot, account_id) else {
323 return BorrowAssetAmount::zero();
324 };
325
326 match mode {
327 HarvestYieldMode::SnapshotLimit(limit) => {
328 supply_position.accumulate_yield_partial(limit);
329 }
330 HarvestYieldMode::Default => {
331 supply_position.accumulate_yield();
332 }
333 }
334
335 BorrowAssetAmount::zero()
336 }
337
338 fn get_last_yield_rate(&self) -> Decimal {
339 self.configuration
340 .supply_yield_rate_from_interest(&self.current_snapshot())
341 }
342
343 fn get_static_yield(&self, account_id: AccountId) -> Option<Accumulator<BorrowAsset>> {
344 self.static_yield.get(&account_id)
345 }
346
347 fn accumulate_static_yield(
348 &mut self,
349 account_id: Option<AccountId>,
350 snapshot_limit: Option<u32>,
351 ) {
352 self.market
353 .accumulate_static_yield(
354 &account_id.unwrap_or_else(env::predecessor_account_id),
355 snapshot_limit.unwrap_or(u32::MAX),
356 )
357 .unwrap_or_else(|_| {
358 templar_common::panic_with_message("This account does not earn static yield")
359 });
360 }
361
362 fn withdraw_static_yield(&mut self, amount: Option<BorrowAssetAmount>) -> Promise {
363 let predecessor = env::predecessor_account_id();
364 let Some(mut yield_record) = self.static_yield.get(&predecessor) else {
365 templar_common::panic_with_message("Yield record does not exist");
366 };
367
368 let available = yield_record.get_total();
369 let amount = amount.unwrap_or(available);
370 require!(
371 amount <= available,
372 "Attempt to withdraw more than accumulated static yield",
373 );
374
375 yield_record.remove(amount);
376
377 self.static_yield.insert(&predecessor, &yield_record);
378 self.borrow_asset_balance = self.borrow_asset_balance.unwrap_sub(
379 amount,
380 "Invariant violation: static yield withdrawal exceeds market borrow asset balance",
381 );
382
383 self.configuration
384 .borrow_asset
385 .transfer(predecessor.clone(), amount)
386 .then(
387 self_ext!(Self::GAS_WITHDRAW_STATIC_YIELD_01_FINALIZE)
388 .withdraw_static_yield_01_finalize(predecessor, amount),
389 )
390 }
391}