Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests and contract rework for audit #69

Merged
merged 45 commits into from
Nov 1, 2024
Merged

Conversation

faurdent
Copy link
Collaborator

No description provided.

@faurdent faurdent marked this pull request as ready for review October 24, 2024 11:35
Copy link
Collaborator

@tensojka tensojka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is decided through which Ekubo pools you'll be routing? (Did you look into that?)

/// * `debt_token`: ContractAddress - Address of the token used as borrowing.
/// * `pool_key`: PoolKey - Ekubo type for obtaining info about the pool and swapping
/// tokens.
/// * `supply_price`: felt252 - Price of `supply` token in terms of `debt` token in Ekubo
Copy link
Collaborator

@tensojka tensojka Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it exactly just price? How is that expressed? It must be a fraction or fixed point number right; what kind is it? (edit: later found in docs)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And – can you give an example where supply_price is > 2**128? (I don't think it can reach that high?)

This format is used in Ekubo? (I don't think so)

I don't think it makes sense to have the price be in relation to one "full" (human-readable) unit; then you need to fiddle with decimals.

So 2 questions:

  1. Why do you need the price? (Future slippage calculation?)
  2. Don't you get the price from Ekubo after executing a swap?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why aren't you using the same format as Ekubo (since that's where you interact with the price) for storing the price? (You will then be able to truly get rid of decimals:) (I understand the Ekubo math is very hard and it might very well be a good reason)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Calculation of how much can I borrow from zkLend, we will borrow 80%(I will try and test lower value) of allowed amount so precise median price from the oracles as used in zkLend is not required I suppose.
  2. It's not given exactly on swap. However I can get pool price from Ekubo as sqrt_ratio and then convert to the normal price. I am concerned about price of base token in terms of quote token(so ETH amount for USDC in ETH/USDC) and I wanted to calculate it on backend with normal decimals, but I should be able to handle it to depend on external params less.
  3. Ekubo using prices as ticks or sqrt_ratio and zkLend as normal amounts, so there is a need for conversions unfortunately.

Comment on lines 261 to 264
let SQRT_LIMIT_REPAY = if supply_token == pool_key.token0 {
18446748437148339061
} else {
(6277100250585753475930931601400621808602321654880405518632, 18446748437148339061)
6277100250585753475930931601400621808602321654880405518632
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially the max slippage no? Is it correct to hardcode it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will calculate 3-5% slippage from current sqrt_ratio

Comment on lines 133 to 136
assert(
amount * usdc_price / deposit_token_decimals.into() >= 1000000,
'Loop amount is too small'
); // User needs to have at least 1 USDC, so rounded down amount is OK.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are only using usdc_price to calculate whether the loan amount is at least 1 USDC.

  1. Why is this limitation in place?
  2. This does not actually check anything because the caller can spoof the usdc_price. (!!)
  3. If this is really necessary (and I don't think it is), you can adjust the usdc_price format such that you don't need to fetch decimals! (Basically embed the decimals there and pass only the ratio)
  4. usdc_price is actually not really price of USDC, is it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it because sometimes for small amounts depositing and borrowing is failing because of number of iterations(or steps made by program). I can remove it but sometimes transaction will fail with unclear message(same will be if user or our frontend inputs wrong price). I had some doubts when adding this because of the same reason, so I will remove it and try to locate the reason and location of fail, it's number of iterations I would consider limiting it instead(number of deposits and borrowings in a loop).

) {
assert(get_caller_address() == self.owner.read(), 'Caller is not an owner');
// TODO: Add borrow factor
let user_acount = get_tx_info().unbox().account_contract_address;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not get_caller_address() ? (Maybe it was removed in a new Cairo version that I'm not yet aware of?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was made because of testing troubles. In a contract, when swapping we are getting callback from Ekubo. When using normal cheat_caller_address, it cheats all input calls to the contract, and code fails with 'Caller is not the core' error from Ekubo core contract, so I cheat the account that initiated transaction instead(practically it should be the caller themself). However I agree this functionality is an unusual and unpredicted, so I should include it in the docs.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think okay to just add an explainer comment like this to the code. Interesting!

is_token1: !is_token1_repay_swap,
sqrt_ratio_limit: SQRT_LIMIT_WITHDRAW,
skip_ahead: 0
debt = zk_market.get_user_debt_for_token(contract_address, debt_token).into();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you previously called repay_all (line 311), this will be zero, no? Either remove or assert that it's zero in that case.

@faurdent faurdent requested a review from tensojka October 29, 2024 21:44
docs/spotnet.md Outdated
Comment on lines 60 to 61
* `supply_price`: TokenPrice - Price of `supply` token in terms of `debt` token in Ekubo pool(so for ex. 2400000000 USDC for ETH).
* `debt_price`: TokenPrice - Price of `debt` token in terms of `supply` token in Ekubo pool(for ex. 410000000000000 ETH for USDC).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really the price in Ekubo pool?

let amount_base_token = token_price * borrow_capacity;
let amount_quote_token = amount_base_token.into() / decimals_difference.into();
((amount_quote_token - total_borrowed.into()) / 100_u256 * borrow_const).try_into().unwrap()
let borrow_const = 60;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on the asset, no? It can change as zkLend changes its risk parameters I think?

But maybe the only thing this will cause is that it will loop more times than necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zkLend risk parameters are borrow and collateral factors and we are getting them from their contract. This one is how much of available amount we are borrowing(approximately 60% now), while causing looping more times, lower value will increase health factor. I can add it to the constructor so we'll be able to change it dynamically without changing the code

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should that matter? Why can't we be borrowing 99 %? The health factor is decided by the leverage at the end only.

let curr_contract_address = get_contract_address();
assert(
token_dispatcher.allowance(user_acount, curr_contract_address) >= amount,
'Approved amount incuficient'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

let mut deposited = amount;
let mut total_borrowed = 0;
let mut accumulated = 0;
let (mut total_borrowed, mut accumulated, mut deposited) = (0, 0, amount);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just keep this on three separate lines

src/types.cairo Outdated
use starknet::ContractAddress;

pub type TokenPrice = u256;
pub type TokenPrice = u128;
pub type TokenAmount = u256;
pub type Decimals = u64;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't actually store Decimals in this data type, you store 10**x where x is the decimal count, which is unclear

proof: Span<felt252>,
airdrop_addr: ContractAddress
) {
assert(self.is_position_open.read(), 'Open position not exists');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the position was closed in the meantime? Why do we need this check?

airdrop_addr: ContractAddress
) {
assert(self.is_position_open.read(), 'Open position not exists');
assert(proof.len() != 0, 'Proof Span cannot be empty');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the defensiveness, but this is not necessary; it will fail when calling claim() anyway

@faurdent faurdent requested a review from tensojka October 30, 2024 16:56
@djeck1432 djeck1432 merged commit 18bd439 into main Nov 1, 2024
3 of 6 checks passed
@djeck1432 djeck1432 deleted the fix/audit-preparation branch November 1, 2024 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants