Skip to content

Commit

Permalink
feat: implement deposit limit (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
zklend-tech authored Aug 20, 2024
1 parent 5c1c665 commit d1a959c
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ trait IMarket<TContractState> {

fn set_debt_limit(ref self: TContractState, token: ContractAddress, limit: felt252);

fn set_deposit_limit(ref self: TContractState, token: ContractAddress, limit: felt252);

fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress);

fn renounce_ownership(ref self: TContractState);
Expand Down Expand Up @@ -316,7 +318,8 @@ struct MarketReserveData {
raw_total_debt: felt252,
flash_loan_fee: felt252,
liquidation_bonus: felt252,
debt_limit: felt252
debt_limit: felt252,
deposit_limit: felt252,
}

#[derive(Drop, Serde)]
Expand Down
11 changes: 11 additions & 0 deletions src/market.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ mod Market {
BorrowFactorUpdate: BorrowFactorUpdate,
ReserveFactorUpdate: ReserveFactorUpdate,
DebtLimitUpdate: DebtLimitUpdate,
DepositLimitUpdate: DepositLimitUpdate,
Deposit: Deposit,
Withdrawal: Withdrawal,
Borrowing: Borrowing,
Expand Down Expand Up @@ -131,6 +132,12 @@ mod Market {
limit: felt252
}

#[derive(Drop, PartialEq, starknet::Event)]
struct DepositLimitUpdate {
token: ContractAddress,
limit: felt252
}

#[derive(Drop, PartialEq, starknet::Event)]
struct Deposit {
user: ContractAddress,
Expand Down Expand Up @@ -384,6 +391,10 @@ mod Market {
external::set_debt_limit(ref self, token, limit)
}

fn set_deposit_limit(ref self: ContractState, token: ContractAddress, limit: felt252) {
external::set_deposit_limit(ref self, token, limit)
}

fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) {
external::transfer_ownership(ref self, new_owner)
}
Expand Down
1 change: 1 addition & 0 deletions src/market/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const BALANCE_OVERFLOW: felt252 = 'MKT_BALANCE_OVERFLOW';
const BORROW_FACTOR_RANGE: felt252 = 'MKT_BORROW_FACTOR_RANGE';
const COLLATERAL_FACTOR_RANGE: felt252 = 'MKT_COLLATERAL_FACTOR_RANGE';
const DEBT_LIMIT_EXCEEDED: felt252 = 'MKT_DEBT_LIMIT_EXCEEDED';
const DEPOSIT_LIMIT_EXCEEDED: felt252 = 'MKT_DEPOSIT_LIMIT_EXCEEDED';
const INSUFFICIENT_AMOUNT_REPAID: felt252 = 'MKT_INSUFFICIENT_AMOUNT_REPAID';
const INSUFFICIENT_COLLATERAL: felt252 = 'MKT_INSUFFICIENT_COLLATERAL';
const INVALID_AMOUNT: felt252 = 'MKT_INVALID_AMOUNT';
Expand Down
9 changes: 9 additions & 0 deletions src/market/external.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ fn add_reserve(
flash_loan_fee,
liquidation_bonus,
debt_limit: 0,
deposit_limit: 0,
};
self.reserves.write(token, new_reserve);

Expand Down Expand Up @@ -371,6 +372,14 @@ fn set_debt_limit(ref self: ContractState, token: ContractAddress, limit: felt25
self.emit(contract::Event::DebtLimitUpdate(contract::DebtLimitUpdate { token, limit }));
}

fn set_deposit_limit(ref self: ContractState, token: ContractAddress, limit: felt252) {
ownable::assert_only_owner(@self);

internal::assert_reserve_exists(@self, token);
self.reserves.write_deposit_limit(token, limit);
self.emit(contract::Event::DepositLimitUpdate(contract::DepositLimitUpdate { token, limit }));
}

fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) {
ownable::transfer_ownership(ref self, new_owner);
}
Expand Down
26 changes: 26 additions & 0 deletions src/market/internal.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ fn deposit(ref self: ContractState, token: ContractAddress, amount: felt252) {
0 // abs_delta_raw_total_debt
);

// Enforces token deposit limit
assert_deposit_limit_satisfied(@self, token, amount);

self
.emit(
contract::Event::Deposit(
Expand Down Expand Up @@ -922,6 +925,29 @@ fn assert_debt_limit_satisfied(self: @ContractState, token: ContractAddress) {
);
}

/// Checks if the deposit limit is satisfied.
fn assert_deposit_limit_satisfied(
self: @ContractState, token: ContractAddress, extra_amount: felt252
) {
let deposit_limit = self.reserves.read_deposit_limit(token);

// Unlike debt limit, a zero deposit limit indicates no limit is in place.
if deposit_limit.is_zero() {
return;
}

let z_token_address = self.reserves.read_z_token_address(token);

// Adding extra amount as checks are performed before external interactions.
let scaled_deposit = IZTokenDispatcher { contract_address: z_token_address }.felt_total_supply()
+ extra_amount;

assert(
Into::<_, u256>::into(scaled_deposit) <= Into::<_, u256>::into(deposit_limit),
errors::DEPOSIT_LIMIT_EXCEEDED
);
}

/// This function is called to distribute excessive reserve assets to depositors. Such extra balance
/// can come from a variety of sources, including direct transfer of tokens into
/// this contract. However, in practice, this function is only called right after a flash loan,
Expand Down
18 changes: 18 additions & 0 deletions src/market/storage.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ trait ReservesStorageShortcuts<T> {

fn read_debt_limit(self: @T, token: ContractAddress) -> felt252;

fn read_deposit_limit(self: @T, token: ContractAddress) -> felt252;

fn read_interest_rate_model_and_raw_total_debt(
self: @T, token: ContractAddress
) -> StorageBatch1;
Expand Down Expand Up @@ -114,6 +116,8 @@ trait ReservesStorageShortcuts<T> {

fn write_debt_limit(self: @T, token: ContractAddress, debt_limit: felt252);

fn write_deposit_limit(self: @T, token: ContractAddress, deposit_limit: felt252);

fn write_accumulators(
self: @T,
token: ContractAddress,
Expand Down Expand Up @@ -187,6 +191,14 @@ impl ReservesStorageShortcutsImpl of ReservesStorageShortcuts<Reserves> {
debt_limit
}

fn read_deposit_limit(self: @Reserves, token: ContractAddress) -> felt252 {
let base = self.address(token);

let deposit_limit = Store::<felt252>::read_at_offset(D, base, 16).expect(E);

deposit_limit
}

fn read_interest_rate_model_and_raw_total_debt(
self: @Reserves, token: ContractAddress
) -> StorageBatch1 {
Expand Down Expand Up @@ -318,6 +330,12 @@ impl ReservesStorageShortcutsImpl of ReservesStorageShortcuts<Reserves> {
Store::<felt252>::write_at_offset(D, base, 15, debt_limit).expect(E);
}

fn write_deposit_limit(self: @Reserves, token: ContractAddress, deposit_limit: felt252) {
let base = self.address(token);

Store::<felt252>::write_at_offset(D, base, 16, deposit_limit).expect(E);
}

fn write_accumulators(
self: @Reserves,
token: ContractAddress,
Expand Down
83 changes: 83 additions & 0 deletions tests/market.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,89 @@ fn test_debt_limit_is_global() {
);
}

#[test]
#[available_gas(90000000)]
#[should_panic(expected: ('MKT_DEPOSIT_LIMIT_EXCEEDED', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))]
fn test_deposit_cannot_exceed_deposit_limit() {
let setup = setup_with_alice_and_bob_deposit();

// Deposit limit set to 20,000 TST_B (Bob already deposited 10,000)
setup
.alice
.market_set_deposit_limit(
setup.market.contract_address,
setup.token_b.contract_address, // token
20000000000000000000000 // limit
);

// Bob can't borrow 11,000 TST_B
setup
.bob
.market_deposit(
setup.market.contract_address,
setup.token_b.contract_address, // token
11000000000000000000000 // amount
);
}

#[test]
#[available_gas(90000000)]
fn test_can_deposit_till_deposit_limit() {
let setup = setup_with_alice_and_bob_deposit();

// Deposit limit set to 20,000 TST_B (Bob already deposited 10,000)
setup
.alice
.market_set_deposit_limit(
setup.market.contract_address,
setup.token_b.contract_address, // token
20000000000000000000000 // limit
);

// Depositing 10,000 TST_B is allowed (exactly at limit)
setup
.bob
.market_deposit(
setup.market.contract_address,
setup.token_b.contract_address, // token
10000000000000000000000 // amount
);
}

#[test]
#[available_gas(90000000)]
#[should_panic(expected: ('MKT_DEPOSIT_LIMIT_EXCEEDED', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))]
fn test_deposit_limit_is_global() {
let setup = setup_with_alice_and_bob_deposit();

// Deposit limit set to 20,000 TST_B (Bob already deposited 10,000)
setup
.alice
.market_set_deposit_limit(
setup.market.contract_address,
setup.token_b.contract_address, // token
20000000000000000000000 // limit
);

// The full limit is used by Bob
setup
.bob
.market_deposit(
setup.market.contract_address,
setup.token_b.contract_address, // token
10000000000000000000000 // amount
);

// Alice cannot deposit anymore as the limit is global
setup
.alice
.market_deposit(
setup.market.contract_address,
setup.token_b.contract_address, // token
1000000000000000000 // amount
);
}

#[test]
#[available_gas(90000000)]
fn test_rate_changes_on_deposit() {
Expand Down
7 changes: 7 additions & 0 deletions tests/mock.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ trait IAccount<TContractState> {
limit: felt252
);

fn market_set_deposit_limit(
ref self: TContractState,
contract_address: ContractAddress,
token: ContractAddress,
limit: felt252
);

fn market_add_reserve(
ref self: TContractState,
contract_address: ContractAddress,
Expand Down
9 changes: 9 additions & 0 deletions tests/mock/account.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ mod Account {
IMarketDispatcher { contract_address }.set_debt_limit(token, limit)
}

fn market_set_deposit_limit(
ref self: ContractState,
contract_address: ContractAddress,
token: ContractAddress,
limit: felt252
) {
IMarketDispatcher { contract_address }.set_deposit_limit(token, limit)
}

fn market_add_reserve(
ref self: ContractState,
contract_address: ContractAddress,
Expand Down

0 comments on commit d1a959c

Please sign in to comment.