-
Notifications
You must be signed in to change notification settings - Fork 61
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
Repay/Withdraw max #40
Comments
The problem is that withdrawer/repayer are often not sync (computing their amount off-chain), and some blocks can pass between their submission and the tx being included. Thus it's convenient for them to have a way to make sure that they withdraw/repay everything. In see two ways of solving this inside of the protocol:
Nb: this is not an issue for user computing their amount synchronously (ie through a smart contract) |
Do you suggest shifting from |
shifting, I really hate duplicates |
I like the shares idea, but then I think it completely removes the ability of a user to interact directly with the protocol.
|
For now I'm more inclined to do the first option as it's a one liner in the end and quite simple. The other options are adding a lot of complexity for something that the protocol can easily do (I know this is an argument quite common) |
I don't think so, cTokens are not rebasing but they are "unusable direclty" right ? |
I'm talking about the lending pool, not the token. In Compound, you pass the amount in underlying that you want to supply. If you had to pass the shares, yes I think Compound would get significantly harder to use directly. As a user, I care about the assets I will transfer and I don't care about the shares (this is the internal accounting of the protocol) |
I think being able to withdraw/repay the maximum amount is a must. We can trigger this logic only if |
Using shares on withdraw/repay is what I suggested here: There's a middle ground where you also expose redeem/redeemBorrow functions that behave like withdraw/repay but expect shares as input (see EIP4626) I am also ok to enable withdraw/repay max, only when input is References in my PoC |
I think I'm for having the capacity to put |
@julien-devatom do you have thoughts on this? |
Using shares is cleaner and also more understandable: you know what you are withdrawing. Input is not "random" where, by using max, you don't really know how much you're withdrawing. more predictable, and you provide what the user is requesting. |
For the UX of the protocol, this is obviously harder, but you can easily have an exchange rate and shift from one to another |
Should we expose those getters? |
Even if you use shares you have to deal with some rounding errors and now it's the underlying amount that will be "random". It's annoying if you want to approve precisely, but more importantly it's less obvious what you will actually receive/give (as underlying amount, which is what matters for the user) |
|
I would be cool to know what "share" of withdraw / repay are withdrawing/repaying max |
A user can already check which share she has no? |
I want to emphasize this, as we've seen twice with the bulker that it's very useful to know how much exactly the bulker will receive (resp. transfer) upon withdrawing (resp. repaying), to avoid leaving dust. For other general usecases, we have the possibility to return the On a side note, I believe that EIP4626 already discussed this. It is the reason I suggest to stick to it. Here's the full discussion lol. |
You mean exposing the 2 entry points (shares and underlying), right? Quite a long discussion 😅 |
Well yes, but it's the default choice of following what others engineered before us. It seems everything is questioned lately, so I'm not sure this habit has wind in its sails... But it's my suggestion. |
Actually I read the discussion and they do talk about shares vs assets starting from here. Out of topic: There are also discussions about naming here. Actually, after reading a lot of it:
Here are my thoughts on the topic of this issue after further reasoning: Issues with using
|
I think this could be very misleading and lead to errors. Because in both cases we would have the issue below, it seems that they will need to infinite approve blue and pass either
|
This is what EIP4626 does, and it seems to not be misleading since the Ethereum community accepted the proposal. If manipulating both amounts is misleading then anything can be misleading at this point. Naming is key. I didn't understand the second part of your comment. |
True. But they accepted proposals that led to many hacks as well (ERC777). So I would be cautious about that type of argument.
Take this example. Let's say your an integrator that wants to repay all of his debt. Underlying-based mechanism:
VS Share-based mechanism:
It seems that 2. is a required and added step for the share-based mechanism. |
That's correct, though you can query it offchain and pass it as parameter. I have added this to my comment above. I may lean towards using Using Is it overkill to expose both? |
I think this is a bit overkill yes haha |
To sum up: do we agree on the following?
|
I stumbled upon something related when working on rounding directions: upon withdrawal, we round up when calculating the shares associated with an input amount. This number of shares can thus be 1 wei higher than the user's shares, resulting in an underflow when writing to storage. So the only thing that I can think of to be able to revert on unexpected withdrawals as well as not revert for amounts associated with shares 1 wei off would be: if(amount == type(uint256).max) shares = userShares;
else {
require(shares <= userShares + 1);
shares = Math.min(shares, userShares);
} Which seems to be a bit complicated for such a behavior vs a simple |
Maybe it puts into question the fact of rounding up on withdraw? |
Summary of our work/discussions: Objective: allowing user, that are potentially asynchronous (EOAs) to withdraw / repay max Solution 1: do nothingonchain integrators would do something like that (see #183): _BLUE.accrueInterests(market, id);
uint256 shares = _BLUE.supplyShare(id, msg.sender);
uint256 totalSupply = _BLUE.totalSupply(id);
uint256 totalSupplyShares = _BLUE.totalSupplyShares(id);
uint256 amount = shares.mulDivDown(totalSupply, totalSupplyShares);
_BLUE.withdraw(market, amount, msg.sender); and offchain users would have to authorize a smart-contract to do that for them gas cost: 81283 (see #183) NB: this should be absolutely coupled with putting accrueInterests public Solution 2: type(uint256).maxon/offchain integrator would do something like that (see #184): _BLUE.withdraw(market, type(uint256).max, msg.sender); +: really simple to repay/withdraw max gas cost: 78420 (see #184) Solution 3: shares as inputon/offchain integrators would do something like that (see #194): blue.withdraw(market, blue.supplyShare(id, address(this)), address(this), receiver); gas cost: unknown +: simpler and cheaper code, more natural to withdraw/repay max, no issues with type(uint256), fixes #177, fixes #155 |
I'm strongly in favor of this solution. It allows users to withdraw/repay the exact amount they want without caring about the shares or having to compute anything on their own. And I don't think it makes the code less readable. I also think discrepancy between supply/borrow - withdraw/repay introduced by Solution 3 would be misleading, and therefore a problem. |
And I'm strongly in favor of a solution using shares as input and exposing corresponding return values, because Blue is low-level primitive where exact inputs are more important than simple inputs for a user-facing friendly API |
In the past is used to allows a user to pass
type(uint256).max
and compute the max. On the current code base it's not possible. Should we?The text was updated successfully, but these errors were encountered: