-
Notifications
You must be signed in to change notification settings - Fork 48
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
Instant unlock + more flexible cliff in LockupLinear #1043
Comments
just some notes as i started to think about an implementation and the benefits of increasing the complexity of the LL model for the shapes, which ultimately has benefit of gas efficiency:
since we will need 2 new storage variables (
the gas improvement is still there, but personally not as much as i thought, which makes me wonder if the extra complexity (in also, note on the pseudocode in the OP, i believe this version would be better: function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) {
// --snip --
if (block.timestamp < cliffTime) {
streamedAmount = depositedAmount * instantUnlockPct;
} else {
initialUnlockedAmount = depositedAmount * instantUnlockPct;
cliffAmountUnlocked = depositedAmount * cliffUnlockPct;
streamableAmount = depositedAmount - (initialUnlockedAmount + cliffAmountUnlocked);
elapsedTimePercentage = (endTime - cliffTime) / (block.timestamp - cliffTime);
streamedAmount = initialUnlockedAmount + cliffAmountUnlocked + (elapsedTimePercentage * streamableAmount);
}
} |
Thanks for looking into it @andreivladbrg. Out of curiosity, why should we not use If we can do that, then it should be the following, right?
|
In your pseudo code,
|
indeed, you are right, it would be better
no, just more conversions, but it should be fine
actually this is not correct we are having this check (we know for sure that the v2-core/src/core/SablierLockupLinear.sol Lines 199 to 202 in 9974448
thus, in your version |
You're right. I missed that its a percentage. |
I have been working on this today and came across some issues:
Click to see solidity tests for gas
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.20 <0.9.0;
contract A {
uint128 internal u128A;
uint128 internal u128B;
mapping(uint256 => uint256) internal _u128As;
mapping(uint256 => uint256) internal _u128Bs;
function setToZero() internal {
u128A = 0;
u128B = 0;
_u128As[1] = 0;
_u128Bs[1] = 0;
}
function setU128NumsAll() internal {
u128A = 123;
u128B = 1234;
}
function setU128NumsSingle() internal {
u128A = 123;
}
function setU128MapsAll() internal {
_u128As[1] = 123;
_u128Bs[1] = 1234;
}
function setU128MapsSingle() internal {
_u128As[1] = 123;
}
// Gas test functions
function testGasSetU128NumsAll() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU128NumsAll();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU128NumsSingle() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU128NumsSingle();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU128MapsAll() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU128MapsAll();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU128MapsSingle() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU128MapsSingle();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testAllGasA() public returns (uint256 gasSetU128NumsAll, uint256 gasSetU128NumsSingle, uint256 gasSetU128MapsAll, uint256 gasSetU128MapsSingle) {
gasSetU128NumsAll = testGasSetU128NumsAll();
gasSetU128NumsSingle = testGasSetU128NumsSingle();
gasSetU128MapsAll = testGasSetU128MapsAll();
gasSetU128MapsSingle = testGasSetU128MapsSingle();
return (gasSetU128NumsAll, gasSetU128NumsSingle, gasSetU128MapsAll, gasSetU128MapsSingle);
}
}
contract B {
uint256 internal u256A;
uint256 internal u256B;
mapping(uint256 => uint256) internal _u256As;
mapping(uint256 => uint256) internal _u256Bs;
function setToZero() internal {
u256A = 0;
u256B = 0;
_u256As[1] = 0;
_u256Bs[1] = 0;
}
function setU256NumsAll() internal {
u256A = 123;
u256B = 1234;
}
function setU256NumsSingle() internal {
u256A = 123;
}
function setU256MapsAll() internal {
_u256As[1] = 123;
_u256Bs[1] = 1234;
}
function setU256MapsSingle() internal {
_u256As[1] = 123;
}
// Gas test functions
function testGasSetU256NumsAll() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU256NumsAll();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU256NumsSingle() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU256NumsSingle();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU256MapsAll() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU256MapsAll();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testGasSetU256MapsSingle() internal returns (uint256) {
setToZero();
uint256 gasBefore = gasleft();
setU256MapsSingle();
uint256 gasAfter = gasleft();
setToZero();
return gasBefore - gasAfter;
}
function testAllGasB() public returns (uint256 gasSetU256NumsAll, uint256 gasSetU256NumsSingle, uint256 gasSetU256MapsAll, uint256 gasSetU256MapsSingle) {
gasSetU256NumsAll = testGasSetU256NumsAll();
gasSetU256NumsSingle = testGasSetU256NumsSingle();
gasSetU256MapsAll = testGasSetU256MapsAll();
gasSetU256MapsSingle = testGasSetU256MapsSingle();
return (gasSetU256NumsAll, gasSetU256NumsSingle, gasSetU256MapsAll, gasSetU256MapsSingle);
}
} |
struct UnlockPercentage {
uin128 instantUnlockPct;
uint128 cliffUnlockPct;
}
mapping(uint256 streamId => UnlockPercentage) internal unlockPercentages; This way, it will be mapped to a single storage. In your example, you are using two mappings therefore they are using separate slots.
|
it wouldn't make a change gas wise. also there could be cases when an user wants:
correct, i was wrong (did a typo), sorry |
I did not understand why it should not make change gas wise. Can you please explain?
Yes thats true. So whether user wants 0 in one value or non-zero in both, the gas usage would be same because its one slot. |
i was wrong, i tested again and indeed it would be more efficient for case when both variables are set, see this result:
|
Discussed in #903
Originally posted by smol-ninja April 16, 2024
Context
In the current design of Lockup linear, the amount unlocked at
cliff
is a function of the time elapsed. That is, if the cliff timestamp is 30% ahead of the start time, this would unlock 30% of the total amount at the cliff. This makes it less flexible for creating a cliff linear vesting.Existing solution
A Cliff linear vesting can be created using Lockup Dynamic (LD). It has a few problems:
Proposed solution
Modify the Lockup Linear design to allow the creation of cliff linear vesting streams. So that,
We might also add two percentages representing: (i) instant unlocked amount (ii) cliff amount.
The text was updated successfully, but these errors were encountered: