1use near_sdk::{env, near, require, serde_json, AccountId, Gas, Promise, PromiseResult};
2use templar_common::{
3 asset::{
4 BorrowAsset, BorrowAssetAmount, CollateralAsset, CollateralAssetAmount, FungibleAsset,
5 ReturnStyle,
6 },
7 borrow::InitialBorrow,
8 market::{LiquidateMsg, Withdrawal},
9 oracle::pyth::OracleResponse,
10 price::PricePair,
11 self_ext,
12 withdrawal_queue::WithdrawalQueueExecutionResult,
13};
14
15use crate::{Contract, ContractExt};
16
17impl Contract {
19 pub fn retrieve_price_pair(&self) -> Promise {
20 self.configuration
21 .price_oracle_configuration
22 .retrieve_price_pair()
23 }
24
25 pub fn price_pair(&self, oracle_response: OracleResponse) -> PricePair {
26 self.configuration
27 .price_oracle_configuration
28 .create_price_pair(&oracle_response)
29 .unwrap_or_else(|e| templar_common::panic_with_message(&e.to_string()))
30 }
31
32 pub fn execute_supply(&mut self, account_id: AccountId, amount: BorrowAssetAmount) {
33 if self.supply_position_ref(account_id.clone()).is_none() {
34 self.charge_for_storage(
35 &account_id,
36 self.storage_usage_supply_position + self.storage_usage_snapshot * 2,
37 );
38 }
39
40 let snapshot = self.snapshot();
41 let mut supply_position = self.get_or_create_supply_position_guard(snapshot, account_id);
42 let proof = supply_position.accumulate_yield();
43 supply_position.record_deposit(proof, amount, env::block_timestamp_ms());
44 require!(
45 supply_position.is_within_allowable_range(),
46 "New supply position is outside of allowable range",
47 );
48 }
49
50 pub fn execute_collateralize(
51 &mut self,
52 account_id: AccountId,
53 amount: CollateralAssetAmount,
54 price_pair: &PricePair,
55 ) {
56 if self.borrow_position_ref(account_id.clone()).is_none() {
64 self.charge_for_storage(
65 &account_id,
66 self.storage_usage_borrow_position + self.storage_usage_snapshot * 2,
67 );
68 }
69
70 let snapshot = self.snapshot();
71 let mut borrow_position = self.get_or_create_borrow_position_guard(snapshot, account_id);
72 let proof = borrow_position.accumulate_interest();
73 borrow_position.record_collateral_asset_deposit(proof, amount);
74 require!(
75 !borrow_position
76 .status(price_pair, env::block_timestamp_ms())
77 .is_liquidation(),
78 "Position is eligible for liquidation after collateralization",
79 );
80 }
81
82 pub fn execute_repay(
84 &mut self,
85 account_id: AccountId,
86 amount: BorrowAssetAmount,
87 price_pair: &PricePair,
88 ) -> BorrowAssetAmount {
89 let snapshot = self.snapshot();
90 let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id) else {
91 return amount;
93 };
94 let proof = borrow_position.accumulate_interest();
95 require!(
96 !borrow_position
97 .status(price_pair, env::block_timestamp_ms())
98 .is_liquidation(),
99 "Cannot repay when eligible for liquidation",
100 );
101 borrow_position.record_repay(proof, amount)
103 }
104}
105
106#[near]
108impl Contract {
109 pub const GAS_BORROW_01_CONSUME_PRICE: Gas = Gas::from_tgas(6)
111 .saturating_add(FungibleAsset::<BorrowAsset>::GAS_FT_TRANSFER)
112 .saturating_add(Self::GAS_BORROW_02_FINALIZE);
113
114 #[private]
115 pub fn borrow_01_consume_price(
116 &mut self,
117 account_id: AccountId,
118 amount: BorrowAssetAmount,
119 #[callback_unwrap] oracle_response: OracleResponse,
120 ) -> Promise {
121 let price_pair = self.price_pair(oracle_response);
122 let snapshot = self.snapshot();
123
124 let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id.clone())
125 else {
126 templar_common::panic_with_message(
127 "No borrower record. Please deposit collateral first.",
128 );
129 };
130
131 let interest = borrow_position.accumulate_interest();
132
133 let initial_borrow = borrow_position
134 .record_borrow_initial(
135 snapshot,
136 interest,
137 amount,
138 &price_pair,
139 env::block_timestamp_ms(),
140 )
141 .unwrap_or_else(|e| templar_common::panic_with_message(&e.to_string()));
142
143 drop(borrow_position);
144
145 self.configuration
146 .borrow_asset
147 .transfer(account_id.clone(), amount)
148 .then(
149 self_ext!(Self::GAS_BORROW_02_FINALIZE)
150 .borrow_02_finalize(account_id, initial_borrow),
151 )
152 }
153
154 pub const GAS_BORROW_02_FINALIZE: Gas = Gas::from_tgas(6);
156
157 #[private]
158 pub fn borrow_02_finalize(&mut self, account_id: AccountId, initial_borrow: InitialBorrow) {
159 let snapshot = self.snapshot();
160 let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id) else {
161 templar_common::panic_with_message(
162 "Invariant violation: borrow position does not exist after transfer.",
163 );
164 };
165
166 let proof = borrow_position.accumulate_interest();
167 #[allow(deprecated)]
168 let success = matches!(env::promise_result(0), PromiseResult::Successful(_));
169 borrow_position.record_borrow_final(
170 snapshot,
171 proof,
172 &initial_borrow,
173 success,
174 env::block_timestamp_ms(),
175 );
176 }
177
178 pub const GAS_EXECUTE_NEXT_SUPPLY_WITHDRAWAL_REQUEST_01_FINALIZE: Gas = Gas::from_tgas(8);
180
181 #[private]
182 pub fn execute_next_supply_withdrawal_request_01_finalize(
183 &mut self,
184 resolutions: Vec<Withdrawal>,
185 result: WithdrawalQueueExecutionResult,
186 ) -> WithdrawalQueueExecutionResult {
187 let snapshot = self.snapshot();
188
189 for (i, resolution) in resolutions.iter().enumerate() {
190 #[allow(deprecated)]
191 let succeeded = matches!(env::promise_result(i as u64), PromiseResult::Successful(_));
192
193 if let Some(mut position) =
194 self.supply_position_guard(snapshot, resolution.account_id.clone())
195 {
196 position.record_withdrawal_final(resolution, succeeded);
197 }
198
199 if succeeded && self.cleanup_supply_position(&resolution.account_id) {
200 self.refund_for_storage(&resolution.account_id, self.storage_usage_supply_position);
201 }
202 }
203
204 result
205 }
206
207 pub const GAS_COLLATERALIZE_TRANSFER_CALL_01_CONSUME_PRICE: Gas = Gas::from_tgas(6);
209
210 #[private]
211 pub fn collateralize_transfer_call_01_consume_price(
212 &mut self,
213 account_id: AccountId,
214 amount: CollateralAssetAmount,
215 return_style: ReturnStyle,
216 #[callback_unwrap] oracle_response: OracleResponse,
217 ) -> serde_json::Value {
218 let price_pair = self.price_pair(oracle_response);
219
220 self.execute_collateralize(account_id, amount, &price_pair);
221
222 return_style.serialize(CollateralAssetAmount::zero())
223 }
224
225 pub const GAS_REPAY_TRANSFER_CALL_01_CONSUME_PRICE: Gas = Gas::from_tgas(7);
227
228 #[private]
229 pub fn repay_transfer_call_01_consume_price(
230 &mut self,
231 account_id: AccountId,
232 amount: BorrowAssetAmount,
233 return_style: ReturnStyle,
234 #[callback_unwrap] oracle_response: OracleResponse,
235 ) -> serde_json::Value {
236 let price_pair = self.price_pair(oracle_response);
237
238 let amount = self.execute_repay(account_id, amount, &price_pair);
239
240 return_style.serialize(amount)
241 }
242
243 pub const GAS_LIQUIDATE_TRANSFER_CALL_01_CONSUME_PRICE: Gas = Gas::from_tgas(7)
245 .saturating_add(FungibleAsset::<CollateralAsset>::GAS_FT_TRANSFER)
246 .saturating_add(Self::GAS_LIQUIDATE_TRANSFER_CALL_02_FINALIZE);
247
248 #[private]
249 pub fn liquidate_transfer_call_01_consume_price(
250 &mut self,
251 liquidator_id: AccountId,
252 amount: BorrowAssetAmount,
253 msg: LiquidateMsg,
254 return_style: ReturnStyle,
255 #[callback_unwrap] oracle_response: OracleResponse,
256 ) -> Promise {
257 let price_pair = self
258 .configuration
259 .price_oracle_configuration
260 .create_price_pair(&oracle_response)
261 .unwrap_or_else(|e| templar_common::panic_with_message(&e.to_string()));
262
263 let result = {
264 let snapshot = self.snapshot();
265 let mut borrow_position = self
266 .borrow_position_guard(snapshot, msg.account_id.clone())
267 .unwrap_or_else(|| {
268 templar_common::panic_with_message("Borrow position does not exist")
269 });
270
271 let proof = borrow_position.accumulate_interest();
272
273 borrow_position
274 .record_liquidation(
275 proof,
276 liquidator_id.clone(),
277 amount,
278 msg.amount,
279 &price_pair,
280 env::block_timestamp_ms(),
281 )
282 .unwrap_or_else(|e| templar_common::panic_with_message(&e.to_string()))
283 };
284
285 if self.cleanup_borrow_position(&msg.account_id) {
286 self.refund_for_storage(&msg.account_id, self.storage_usage_borrow_position);
287 }
288
289 self.configuration
290 .collateral_asset
291 .transfer(liquidator_id.clone(), result.liquidated)
292 .then(
293 self_ext!(Self::GAS_LIQUIDATE_TRANSFER_CALL_02_FINALIZE)
294 .liquidate_transfer_call_02_finalize(result.refund, return_style),
295 )
296 }
297
298 pub const GAS_LIQUIDATE_TRANSFER_CALL_02_FINALIZE: Gas = Gas::from_tgas(4);
300
301 #[private]
304 pub fn liquidate_transfer_call_02_finalize(
305 &mut self,
306 refund: BorrowAssetAmount,
307 return_style: ReturnStyle,
308 ) -> serde_json::Value {
309 return_style.serialize(refund)
323 }
324
325 pub const GAS_WITHDRAW_COLLATERAL_01_CONSUME_PRICE: Gas = Gas::from_tgas(7)
327 .saturating_add(FungibleAsset::<CollateralAsset>::GAS_FT_TRANSFER)
328 .saturating_add(Self::GAS_WITHDRAW_COLLATERAL_02_FINALIZE);
329
330 #[private]
331 pub fn withdraw_collateral_01_consume_price(
332 &mut self,
333 account_id: AccountId,
334 amount: CollateralAssetAmount,
335 #[callback_unwrap] oracle_response: OracleResponse,
336 ) -> Promise {
337 let price_pair = self.price_pair(oracle_response);
338
339 let snapshot = self.snapshot();
340 let Some(mut borrow_position) = self.borrow_position_guard(snapshot, account_id.clone())
341 else {
342 templar_common::panic_with_message(
343 "No borrower record. Please deposit collateral first.",
344 );
345 };
346
347 let proof = borrow_position.accumulate_interest();
348 borrow_position.record_collateral_asset_withdrawal_initial(proof, amount);
349
350 require!(
351 borrow_position
352 .status(&price_pair, env::block_timestamp_ms())
353 .is_healthy(),
354 "Borrow position must be healthy after collateral withdrawal",
355 );
356
357 drop(borrow_position);
358
359 self.configuration
360 .collateral_asset
361 .transfer(account_id.clone(), amount)
362 .then(
363 self_ext!(Self::GAS_WITHDRAW_COLLATERAL_02_FINALIZE)
364 .withdraw_collateral_02_finalize(account_id, amount),
365 )
366 }
367
368 pub const GAS_WITHDRAW_COLLATERAL_02_FINALIZE: Gas = Gas::from_tgas(5);
370
371 #[private]
372 pub fn withdraw_collateral_02_finalize(
373 &mut self,
374 account_id: AccountId,
375 amount: CollateralAssetAmount,
376 ) {
377 #[allow(deprecated)]
378 let succeeded = matches!(env::promise_result(0), PromiseResult::Successful(_));
379
380 let snapshot = self.snapshot();
381 let Some(mut position) = self.borrow_position_guard(snapshot, account_id.clone()) else {
382 templar_common::panic_with_message(
383 "Invariant violation: Borrow position must exist after collateral withdrawal.",
384 );
385 };
386
387 let proof = position.accumulate_interest();
388 position.record_collateral_asset_withdrawal_final(proof, amount, succeeded);
389
390 if succeeded {
391 drop(position);
392 if self.cleanup_borrow_position(&account_id) {
393 self.refund_for_storage(&account_id, self.storage_usage_borrow_position);
394 }
395 }
396 }
397
398 pub const GAS_WITHDRAW_STATIC_YIELD_01_FINALIZE: Gas = Gas::from_tgas(5);
400
401 #[private]
402 pub fn withdraw_static_yield_01_finalize(
403 &mut self,
404 account_id: AccountId,
405 amount: BorrowAssetAmount,
406 ) {
407 #[allow(deprecated)]
408 if matches!(env::promise_result(0), PromiseResult::Failed) {
409 let mut yield_record = self.static_yield.get(&account_id).unwrap_or_else(|| {
410 templar_common::panic_with_message(
411 "Invariant violation: static yield entry must exist during callback",
412 )
413 });
414 yield_record.add_once(amount);
415 self.static_yield.insert(&account_id, &yield_record);
416 self.borrow_asset_balance += amount;
417 }
418 }
419}