Skip to content

Commit eb622a0

Browse files
authored
fix: write yield record back to storage on static collateral withdrawal failure (#286)
1 parent 5eaea84 commit eb622a0

File tree

2 files changed

+173
-4
lines changed

2 files changed

+173
-4
lines changed

contract/market/src/impl_helper.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,12 @@ impl Contract {
403403
account_id: AccountId,
404404
amount: BorrowAssetAmount,
405405
) {
406-
let mut yield_record = self.static_yield.get(&account_id).unwrap_or_else(|| {
407-
env::panic_str("Invariant violation: static yield entry must exist during callback")
408-
});
409-
410406
if matches!(env::promise_result(0), PromiseResult::Failed) {
407+
let mut yield_record = self.static_yield.get(&account_id).unwrap_or_else(|| {
408+
env::panic_str("Invariant violation: static yield entry must exist during callback")
409+
});
411410
yield_record.add_once(amount);
411+
self.static_yield.insert(&account_id, &yield_record);
412412
}
413413
}
414414
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use std::time::Duration;
2+
3+
use near_sdk::{serde_json::json, NearToken};
4+
use near_workspaces::{network::Sandbox, Worker};
5+
use rstest::rstest;
6+
use templar_common::{asset::BorrowAssetAmount, dec, interest_rate_strategy::InterestRateStrategy};
7+
use test_utils::*;
8+
9+
#[rstest]
10+
#[tokio::test]
11+
async fn static_yield_success(#[future(awt)] worker: Worker<Sandbox>) {
12+
setup_test!(
13+
worker
14+
extract(c, protocol_yield_user, insurance_yield_user)
15+
accounts(borrow_user, supply_user)
16+
config(|c| {
17+
c.borrow_interest_rate_strategy = InterestRateStrategy::linear(dec!("1000"), dec!("1000")).unwrap();
18+
})
19+
);
20+
21+
tokio::join!(
22+
c.supply_and_harvest_until_activation(&supply_user, 10_000_000),
23+
c.collateralize(&borrow_user, 2_000_000),
24+
);
25+
26+
let record_before = c.get_static_yield(protocol_yield_user.id()).await;
27+
assert_eq!(record_before, None);
28+
29+
c.accumulate_static_yield(&protocol_yield_user, None, None)
30+
.await;
31+
32+
let record_after_noop_accumulate = c
33+
.get_static_yield(protocol_yield_user.id())
34+
.await
35+
.unwrap()
36+
.get_total();
37+
assert_eq!(record_after_noop_accumulate, 0.into());
38+
39+
c.borrow(&borrow_user, 1_000_000).await;
40+
tokio::time::sleep(Duration::from_secs(10)).await;
41+
c.repay(&borrow_user, 1_200_000).await;
42+
43+
let record_after_repay = c
44+
.get_static_yield(protocol_yield_user.id())
45+
.await
46+
.unwrap()
47+
.get_total();
48+
assert_eq!(record_after_repay, 0.into());
49+
50+
c.accumulate_static_yield(&protocol_yield_user, None, None)
51+
.await;
52+
53+
let record_after_accumulate = c
54+
.get_static_yield(protocol_yield_user.id())
55+
.await
56+
.unwrap()
57+
.get_total();
58+
assert_ne!(record_after_accumulate, 0.into());
59+
60+
c.accumulate_static_yield(
61+
&protocol_yield_user,
62+
Some(insurance_yield_user.id().clone()),
63+
None,
64+
)
65+
.await;
66+
67+
// Insurance user hasn't done anything yet
68+
let second_record_after_accumulate = c
69+
.get_static_yield(insurance_yield_user.id())
70+
.await
71+
.unwrap()
72+
.get_total();
73+
assert!(second_record_after_accumulate >= record_after_accumulate);
74+
75+
let balance_before = c.borrow_asset.balance_of(protocol_yield_user.id()).await;
76+
77+
// Ensure withdrawing works properly
78+
c.withdraw_static_yield(&protocol_yield_user, Some(1.into()))
79+
.await;
80+
81+
let balance_after_withdraw_1 = c.borrow_asset.balance_of(protocol_yield_user.id()).await;
82+
83+
assert_eq!(balance_before + 1, balance_after_withdraw_1);
84+
85+
let record_after_withdraw_1 = c
86+
.get_static_yield(protocol_yield_user.id())
87+
.await
88+
.unwrap()
89+
.get_total();
90+
91+
assert_eq!(
92+
record_after_withdraw_1 + BorrowAssetAmount::from(1),
93+
record_after_accumulate,
94+
);
95+
96+
// Withdraw all
97+
c.withdraw_static_yield(&protocol_yield_user, None).await;
98+
99+
let record_after_withdraw_all = c
100+
.get_static_yield(protocol_yield_user.id())
101+
.await
102+
.unwrap()
103+
.get_total();
104+
105+
assert_eq!(record_after_withdraw_all, 0.into());
106+
107+
let balance_after_withdraw_all = c.borrow_asset.balance_of(protocol_yield_user.id()).await;
108+
109+
assert_eq!(
110+
balance_before + u128::from(record_after_accumulate),
111+
balance_after_withdraw_all,
112+
);
113+
}
114+
115+
#[rstest]
116+
#[tokio::test]
117+
async fn static_yield_fail_storage_unregistered(#[future(awt)] worker: Worker<Sandbox>) {
118+
setup_test!(
119+
worker
120+
extract(c, protocol_yield_user, insurance_yield_user)
121+
accounts(borrow_user, supply_user)
122+
config(|c| {
123+
c.borrow_interest_rate_strategy = InterestRateStrategy::linear(dec!("1000"), dec!("1000")).unwrap();
124+
})
125+
);
126+
127+
tokio::join!(
128+
c.supply_and_harvest_until_activation(&supply_user, 10_000_000),
129+
c.collateralize(&borrow_user, 2_000_000),
130+
);
131+
132+
c.borrow(&borrow_user, 1_000_000).await;
133+
tokio::time::sleep(Duration::from_secs(10)).await;
134+
c.repay(&borrow_user, 1_200_000).await;
135+
136+
c.accumulate_static_yield(&protocol_yield_user, None, None)
137+
.await;
138+
139+
let record_after_accumulate = c
140+
.get_static_yield(protocol_yield_user.id())
141+
.await
142+
.unwrap()
143+
.get_total();
144+
eprintln!("Record after accumulate: {record_after_accumulate}");
145+
assert_ne!(record_after_accumulate, 0.into());
146+
147+
let r = protocol_yield_user
148+
.call(c.borrow_asset.contract().id(), "patch_storage_unregister")
149+
.args_json(json!({"force": true}))
150+
.deposit(NearToken::from_yoctonear(1))
151+
.transact()
152+
.await
153+
.unwrap()
154+
.into_result()
155+
.unwrap();
156+
157+
eprintln!("Storage unregister: {r:?}");
158+
159+
let r = c.withdraw_static_yield(&protocol_yield_user, None).await;
160+
eprintln!("Withdraw static yield: {r:?}");
161+
162+
let record_after_failed_withdrawal = c
163+
.get_static_yield(protocol_yield_user.id())
164+
.await
165+
.unwrap()
166+
.get_total();
167+
eprintln!("Record after failed withdrawal: {record_after_failed_withdrawal}");
168+
assert_eq!(record_after_failed_withdrawal, record_after_accumulate);
169+
}

0 commit comments

Comments
 (0)