Skip to content

Commit 4367146

Browse files
authored
Merge branch 'djeck1432:main' into zklend-liquidation-research
2 parents 32ef95f + 49c6717 commit 4367146

File tree

9 files changed

+251
-28
lines changed

9 files changed

+251
-28
lines changed

src/deposit.cairo

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#[starknet::contract]
22
mod Deposit {
3-
use alexandria_math::fast_power::fast_power;
3+
use openzeppelin::access::ownable::interface::OwnableTwoStepABI;
4+
use super::super::interfaces::IVaultDispatcherTrait;
5+
use alexandria_math::fast_power::fast_power;
46
use core::num::traits::Zero;
57

68
use ekubo::{
@@ -17,11 +19,11 @@ mod Deposit {
1719
constants::{ZK_SCALE_DECIMALS, STRK_ADDRESS},
1820
interfaces::{
1921
IMarketDispatcher, IMarketDispatcherTrait, IAirdropDispatcher, IAirdropDispatcherTrait,
20-
IDeposit
22+
IDeposit, IVaultDispatcher
2123
},
2224
types::{
2325
SwapData, SwapResult, DepositData, Claim, EkuboSlippageLimits, TokenAmount, TokenPrice,
24-
DecimalScale
26+
DecimalScale, VaultRepayData
2527
}
2628
};
2729

@@ -172,6 +174,31 @@ mod Deposit {
172174
self.ekubo_core.read(), @swap_data
173175
)
174176
}
177+
fn repay_vaults(ref self: ContractState, supply_token: ContractAddress, vaults: Span<VaultRepayData>){
178+
let zk_market = self.zk_market.read();
179+
let contract_address = get_contract_address();
180+
let owner = self.owner();
181+
182+
for vault in vaults {
183+
let vault_disp = IVaultDispatcher{contract_address: (*vault).vault};
184+
let vault_token = vault_disp.get_vault_token();
185+
if vault_token == supply_token {
186+
continue;
187+
}
188+
let mut amount = (*vault).amount;
189+
let token_disp = ERC20ABIDispatcher { contract_address: vault_token };
190+
191+
if amount == 0 {
192+
zk_market.withdraw_all(vault_token);
193+
amount = token_disp.balanceOf(contract_address);
194+
} else {
195+
zk_market.withdraw(vault_token, amount.try_into().unwrap());
196+
}
197+
198+
token_disp.approve((*vault).vault, amount);
199+
vault_disp.return_liquidity(owner, amount);
200+
}
201+
}
175202
}
176203

177204
#[abi(embed_v0)]
@@ -316,6 +343,7 @@ mod Deposit {
316343
/// * `repay_const`: u8 - Sets how much to borrow from free amount.
317344
/// * `supply_price`: TokenPrice - Price of `supply` token in terms of `debt` token.
318345
/// * `debt_price`: TokenPrice - Price of `debt` token in terms of `supply` token.
346+
/// * `repay_vaults`: Span<VaultRepayData> - Vaults that need to be repaid.
319347
fn close_position(
320348
ref self: ContractState,
321349
supply_token: ContractAddress,
@@ -324,7 +352,8 @@ mod Deposit {
324352
ekubo_limits: EkuboSlippageLimits,
325353
borrow_portion_percent: u8,
326354
supply_price: TokenPrice,
327-
debt_price: TokenPrice
355+
debt_price: TokenPrice,
356+
repay_vaults: Span<VaultRepayData>
328357
) {
329358
assert(
330359
get_tx_info().unbox().account_contract_address == self.ownable.owner(),
@@ -418,6 +447,7 @@ mod Deposit {
418447
};
419448
zk_market.withdraw_all(supply_token);
420449
zk_market.disable_collateral(supply_token);
450+
self.repay_vaults(supply_token, repay_vaults);
421451
self.is_position_open.write(false);
422452
let withdrawn_amount = token_disp.balanceOf(contract_address);
423453
token_disp.transfer(self.ownable.owner(), withdrawn_amount);

src/interfaces.cairo

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ekubo::types::keys::PoolKey;
22
use spotnet::types::{
3-
MarketReserveData, DepositData, Claim, EkuboSlippageLimits, TokenPrice, TokenAmount
3+
MarketReserveData, DepositData, Claim, EkuboSlippageLimits, TokenPrice, TokenAmount, VaultRepayData
44
};
55
use starknet::ContractAddress;
66

@@ -22,7 +22,8 @@ pub trait IDeposit<TContractState> {
2222
ekubo_limits: EkuboSlippageLimits,
2323
borrow_portion_percent: u8,
2424
supply_price: TokenPrice,
25-
debt_price: TokenPrice
25+
debt_price: TokenPrice,
26+
repay_vaults: Span<VaultRepayData>
2627
);
2728

2829
fn claim_reward(
@@ -72,5 +73,7 @@ pub trait IVault<TContractState> {
7273
user: ContractAddress,
7374
amount: TokenAmount
7475
);
76+
fn return_liquidity(ref self: TContractState, user: ContractAddress, amount: TokenAmount);
77+
fn get_vault_token(self: @TContractState) -> ContractAddress;
7578
}
7679

src/mocks/erc20_mock.cairo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[starknet::contract]
22
pub mod SnakeERC20Mock {
3-
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
3+
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
44
use starknet::ContractAddress;
55

66
component!(path: ERC20Component, storage: erc20, event: ERC20Event);

src/types.cairo

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ pub struct Claim {
3838
pub amount: u128
3939
}
4040

41+
#[derive(Copy, Drop, Serde)]
42+
pub struct VaultRepayData {
43+
pub vault: ContractAddress,
44+
pub amount: TokenAmount
45+
}
46+
4147
#[derive(Drop, Serde, starknet::Store)]
4248
pub struct MarketReserveData {
4349
pub enabled: bool,

src/vault.cairo

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ mod Vault {
4747
LiquidityAdded: LiquidityAdded,
4848
LiquidityWithdrawn: LiquidityWithdrawn,
4949
ContractAdded: ContractAdded,
50-
PositionProtected: PositionProtected
50+
PositionProtected: PositionProtected,
51+
LiquidityReturned: LiquidityReturned
5152
}
5253

5354
#[derive(Drop, starknet::Event)]
@@ -89,6 +90,15 @@ mod Vault {
8990
amount: TokenAmount,
9091
}
9192

93+
#[derive(Drop, starknet::Event)]
94+
struct LiquidityReturned {
95+
#[key]
96+
user: ContractAddress,
97+
#[key]
98+
token: ContractAddress,
99+
amount: TokenAmount,
100+
}
101+
92102

93103
#[constructor]
94104
fn constructor(ref self: ContractState, owner: ContractAddress, token: ContractAddress) {
@@ -251,5 +261,36 @@ mod Vault {
251261

252262
self.emit(PositionProtected { token, deposit_contract, contract_owner: user, amount });
253263
}
264+
265+
/// Return liquidity from the vault by transferring tokens to the user.
266+
///
267+
/// # Arguments
268+
///
269+
/// * `user` - The address of the recipient
270+
/// * `amount` - The amount of tokens to send from the vault
271+
///
272+
/// # Events
273+
///
274+
/// Emits a `LiquidityReturned` event with:
275+
/// * `user` - The address of the recipient
276+
/// * `token` - The address of the token
277+
/// * `amount` - The amount of tokens that send from the vault
278+
fn return_liquidity(ref self: ContractState, user: ContractAddress, amount: TokenAmount){
279+
let token = self.token.read();
280+
let current_amount = self.amounts.entry(user).read();
281+
282+
// update new amount
283+
self.amounts.entry(user).write(current_amount + amount);
284+
285+
// transfer token to user
286+
IERC20Dispatcher { contract_address: token }.transfer_from(get_caller_address(), user, amount);
287+
288+
self.emit(LiquidityReturned { user, token, amount });
289+
}
290+
291+
/// Returns the token address stored in the vault
292+
fn get_vault_token(self: @ContractState) -> ContractAddress {
293+
return self.token.read();
294+
}
254295
}
255296
}

tests/test_defispring.cairo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use openzeppelin_token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait};
1+
use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait};
22
use snforge_std::{
33
declare, DeclareResultTrait, replace_bytecode, store, cheat_account_contract_address, CheatSpan
44
};

tests/test_loop.cairo

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ use snforge_std::cheatcodes::execution_info::block_timestamp::{
1515
use snforge_std::cheatcodes::execution_info::caller_address::{
1616
start_cheat_caller_address, stop_cheat_caller_address
1717
};
18-
use spotnet::constants::ZK_SCALE_DECIMALS;
18+
use spotnet::constants::{ZK_SCALE_DECIMALS, STRK_ADDRESS};
1919
use spotnet::interfaces::{
2020
IDepositDispatcher, IDepositSafeDispatcher, IDepositSafeDispatcherTrait, IDepositDispatcherTrait
2121
};
22-
use spotnet::types::DepositData;
22+
use spotnet::types::{DepositData, VaultRepayData};
2323

2424
use starknet::{ContractAddress, get_block_timestamp};
2525
use super::constants::{contracts, tokens, HYPOTHETICAL_OWNER_ADDR, pool_key};
2626

2727
use super::interfaces::{IMarketTestingDispatcher, IMarketTestingDispatcherTrait};
28-
use super::utils::{deploy_deposit_contract, get_asset_price_pragma, get_slippage_limits};
28+
use super::utils::{
29+
deploy_deposit_contract,
30+
get_asset_price_pragma,
31+
get_slippage_limits,
32+
setup_test_suite,
33+
assert_vault_amount_bigger_than_zero
34+
};
2935

3036
fn get_deposit_dispatcher(user: ContractAddress) -> IDepositDispatcher {
3137
IDepositDispatcher { contract_address: deploy_deposit_contract(user) }
@@ -375,7 +381,8 @@ fn test_close_position_usdc_valid_time_passed() {
375381
get_slippage_limits(pool_key),
376382
95,
377383
pool_price,
378-
quote_token_price
384+
quote_token_price,
385+
[].span()
379386
);
380387

381388
stop_cheat_block_timestamp(contracts::ZKLEND_MARKET.try_into().unwrap());
@@ -432,7 +439,8 @@ fn test_close_position_amounts_cleared() {
432439
get_slippage_limits(pool_key),
433440
95,
434441
pool_price.try_into().unwrap(),
435-
quote_token_price
442+
quote_token_price,
443+
[].span()
436444
);
437445
stop_cheat_account_contract_address(deposit_disp.contract_address);
438446
let zk_market = IMarketTestingDispatcher {
@@ -506,7 +514,8 @@ fn test_close_position_partial_debt_utilization() {
506514
get_slippage_limits(pool_key),
507515
95,
508516
pool_price,
509-
quote_token_price
517+
quote_token_price,
518+
[].span()
510519
);
511520
stop_cheat_account_contract_address(deposit_disp.contract_address);
512521
let zk_market = IMarketTestingDispatcher {
@@ -662,7 +671,8 @@ fn test_extra_deposit_supply_token_close_position_fuzz(extra_amount: u32) {
662671
get_slippage_limits(pool_key),
663672
95,
664673
pool_price,
665-
quote_token_price.try_into().unwrap()
674+
quote_token_price.try_into().unwrap(),
675+
[].span()
666676
);
667677
stop_cheat_account_contract_address(deposit_disp.contract_address);
668678

@@ -742,7 +752,8 @@ fn test_withdraw_valid_fuzz(amount: u32) {
742752
get_slippage_limits(pool_key),
743753
95,
744754
pool_price,
745-
quote_token_price
755+
quote_token_price,
756+
[].span()
746757
);
747758
stop_cheat_account_contract_address(deposit_disp.contract_address);
748759

@@ -920,3 +931,96 @@ fn test_transfer_renounced_ownership() {
920931

921932
assert(ownable_disp.owner() == new_owner, 'Owner did not change');
922933
}
934+
935+
#[test]
936+
#[fork("MAINNET")]
937+
fn test_repay_vaults_in_close_position(){
938+
let usdc_addr: ContractAddress = tokens::USDC.try_into().unwrap();
939+
let eth_addr: ContractAddress = tokens::ETH.try_into().unwrap();
940+
let strk_addr: ContractAddress = STRK_ADDRESS.try_into().unwrap();
941+
let user: ContractAddress = HYPOTHETICAL_OWNER_ADDR.try_into().unwrap();
942+
let amount = 1000000;
943+
944+
let repay_vaults = array![
945+
// First will be skip in repay_vaults, because usdc_addr == supply_token
946+
VaultRepayData{vault: setup_test_suite(user, usdc_addr).vault.contract_address, amount},
947+
VaultRepayData{vault: setup_test_suite(user, eth_addr).vault.contract_address, amount},
948+
VaultRepayData{vault: setup_test_suite(user, strk_addr).vault.contract_address, amount}
949+
];
950+
951+
let pool_key = PoolKey {
952+
token0: eth_addr,
953+
token1: usdc_addr,
954+
fee: pool_key::FEE,
955+
tick_spacing: pool_key::TICK_SPACING,
956+
extension: pool_key::EXTENSION.try_into().unwrap()
957+
};
958+
let quote_token_price = get_asset_price_pragma('ETH/USD').into();
959+
960+
let usdc_token_disp = ERC20ABIDispatcher { contract_address: usdc_addr };
961+
let eth_token_disp = ERC20ABIDispatcher { contract_address: eth_addr };
962+
let strk_token_disp = ERC20ABIDispatcher { contract_address: strk_addr };
963+
964+
let decimals_sum_power: u128 = fast_power(
965+
10,
966+
(eth_token_disp.decimals() + usdc_token_disp.decimals())
967+
.into()
968+
);
969+
let pool_price = ((1
970+
* ZK_SCALE_DECIMALS
971+
* decimals_sum_power.into()
972+
/ get_asset_price_pragma('ETH/USD').into())
973+
/ ZK_SCALE_DECIMALS)
974+
.try_into()
975+
.unwrap();
976+
977+
let deposit_disp = get_deposit_dispatcher(user);
978+
let borrow_portion_percent = 90;
979+
let multiplier = 35;
980+
let limits = get_slippage_limits(pool_key);
981+
982+
start_cheat_caller_address(usdc_addr, user);
983+
usdc_token_disp.approve(deposit_disp.contract_address, amount);
984+
stop_cheat_caller_address(usdc_addr);
985+
986+
start_cheat_caller_address(strk_addr, user);
987+
strk_token_disp.approve(deposit_disp.contract_address, amount);
988+
stop_cheat_caller_address(strk_addr);
989+
990+
start_cheat_caller_address(eth_addr, user);
991+
eth_token_disp.approve(deposit_disp.contract_address, amount);
992+
stop_cheat_caller_address(eth_addr);
993+
994+
start_cheat_account_contract_address(deposit_disp.contract_address, user);
995+
deposit_disp
996+
.loop_liquidity(
997+
DepositData {
998+
token: usdc_addr, amount,
999+
multiplier, borrow_portion_percent
1000+
},
1001+
pool_key, limits, pool_price
1002+
);
1003+
stop_cheat_account_contract_address(deposit_disp.contract_address);
1004+
1005+
start_cheat_caller_address(deposit_disp.contract_address, user);
1006+
deposit_disp.extra_deposit(strk_addr, amount);
1007+
deposit_disp.extra_deposit(eth_addr, amount);
1008+
stop_cheat_caller_address(deposit_disp.contract_address);
1009+
1010+
start_cheat_account_contract_address(deposit_disp.contract_address, user);
1011+
start_cheat_block_timestamp(
1012+
contracts::ZKLEND_MARKET.try_into().unwrap(), get_block_timestamp() + 40000000
1013+
);
1014+
deposit_disp
1015+
.close_position(
1016+
usdc_addr, eth_addr, pool_key, limits,
1017+
borrow_portion_percent, pool_price, quote_token_price,
1018+
repay_vaults.span()
1019+
);
1020+
stop_cheat_block_timestamp(contracts::ZKLEND_MARKET.try_into().unwrap());
1021+
stop_cheat_account_contract_address(deposit_disp.contract_address);
1022+
1023+
for vault in repay_vaults {
1024+
assert_vault_amount_bigger_than_zero(vault.vault, user);
1025+
}
1026+
}

0 commit comments

Comments
 (0)