-
Notifications
You must be signed in to change notification settings - Fork 0
Create vlPUFFER #3
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
Conversation
src/vlPUFFER.sol
Outdated
| // 1% in basis points | ||
| uint256 internal constant _KICKER_FEE_BPS = 100; | ||
| // 10000 in basis points | ||
| uint256 internal constant _KICKER_FEE_DENOMINATOR = 10000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When writing numbers in basis points I like to write them like this 100_00 for clarity. I don't really mind, just a suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean 10_000 ? :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, no. I mean 100_00. And 1_00 for _KICKER_FEE_BPS. It's like indicating 100.00% or 1.00%. Since it's basis points the last 2 positions are decimals. 10_000 is even more confusing :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a configuration so that it automatically formats thousands when you do forge fmt
number_underscore = "thousands"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good config, but for this case: meh. We should add a // forgefmt: disable-next-line because that's a confusing way to write basis points
src/vlPUFFER.sol
Outdated
| uint256 pufferAmount, | ||
| uint256 pufferAmountAdded, | ||
| uint256 unlockTime, | ||
| uint256 vlPUFFERAmount |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're showing the increase in pufferAmount, should we do the same in vlPUFFERAmount? Not sure about this since it can be calculated from the data of the event + the timestamp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe remove it completely?, There are transfer events in the PUFFER token, we can figure it out if needed, also it can be read from the calldata. Maybe we don't need it in the event at all pufferAmountAdded wyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm from a BE it can be calculated without problem, but for manual inspection of a tx in the explorer the info in the event might be useful. I'd rather still have it but I don't really mind if it's removed. Your call
| * @notice Withdraw the tokens | ||
| * @param recipient Address to receive the PUFFER tokens | ||
| */ | ||
| function withdraw(address recipient) external { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to implement an emergencyWithdraw function or nah? As I understand it, it would be just like this but ignoring the TokensLocked check and only whenPaused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a discussion on this. Without additional context, its really useless. We need to plan for the future and figure out if ti makes sense to add it.
src/vlPUFFER.sol
Outdated
| function _calculateUnlockTimeAndMultiplier(uint256 unlockTime) | ||
| internal | ||
| view | ||
| returns (uint256 roundedUnlockTime, uint256 multiplier) | ||
| { | ||
| multiplier = ((unlockTime - block.timestamp) / _LOCK_TIME_MULTIPLIER); | ||
| // Round down the unlockTime to the nearest 30-day multiplier | ||
| roundedUnlockTime = block.timestamp + (multiplier * _LOCK_TIME_MULTIPLIER); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if user tries to lock for 59 days he will get 30 days instead. This is unintuitive and unexpected I'd say. I'd suggest instead of unlock time in create lock we can ask for the multiplier directly (between 1 and 24). You can call it multiplier or months
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. The frontend will always use a bigger timestamp to achieve the desired result.
Scenario:
User is on our frontend, and wants to lock for 1 year = x12 multiplier. When the frontend prepares the transaction, the timestamp will be:
unlockTImestamp = now + 1 year in seconds + 30 days in seconds (if a user uses really low gas price, and it takes a few days for the transaction to be included). This will round down in the smart contract, and the user will get the desired multiplier.
It can be refactored to use multiplier instead of the unlockTime. We should also have a sync with the business team before auditing the code.
src/vlPUFFER.sol
Outdated
| function createLockWithPermit(uint256 value, uint256 unlockTime, uint256 deadline, uint8 v, bytes32 r, bytes32 s) | ||
| external | ||
| { | ||
| IERC20Permit(address(PUFFER)).permit(msg.sender, address(this), value, deadline, v, r, s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where is the transfer of PUFFER after permit? bug?
src/vlPUFFER.sol
Outdated
| * @param r First 32 bytes of the signature | ||
| * @param s Second 32 bytes of the signature | ||
| */ | ||
| function createLockWithPermit(uint256 value, uint256 unlockTime, uint256 deadline, uint8 v, bytes32 r, bytes32 s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would suggest to use this pattern for permit as we are already following this pattern on our frontend:
Permit calldata permitData
and then:
permitData.v, ....
and also use try() catch() for permit so that we don't get internal error on etherscan.
src/vlPUFFER.sol
Outdated
| multiplier = ((unlockTime - block.timestamp) / _LOCK_TIME_MULTIPLIER); | ||
| // Round down the unlockTime to the nearest 30-day multiplier | ||
| roundedUnlockTime = block.timestamp + (multiplier * _LOCK_TIME_MULTIPLIER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auditors might raise issue of underflow/overflow here. and suggest to use SafeMath and checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i did cast block timestamp to uint256, to avoid this issue. good catch
| uint256 internal constant _KICKER_FEE_DENOMINATOR = 10_000; | ||
| // 100% in basis points | ||
| // forgefmt: disable-next-line | ||
| uint256 internal constant _KICKER_FEE_DENOMINATOR = 100_00; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100_00 ? it should be 10_000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually we don't even need this as a constant. the term BPS already implies 10,000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, but since it is not upgradeable contract, and we are hardcoding it. it might as well be a readable. this suggestion was made by @eladiosch because 10k bps = 100 %, so i just followed up on it
src/vlPUFFER.sol
Outdated
| // User may deposit more tokens to old lock, or extend the lock | ||
| require(unlockTime >= lockInfo.unlockTime, UnlockTimeMustBeGreaterOrEqualThanLock()); | ||
| require(lockInfo.pufferAmount > 0, LockDoesNotExist()); | ||
|
|
||
| (uint256 roundedUnlockTime, uint256 multiplier) = _calculateUnlockTimeAndMultiplier(unlockTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if rounded unlock time is the same as unlock time?
| require(lockInfo.pufferAmount > 0, LockDoesNotExist()); | ||
| require(lockInfo.unlockTime <= block.timestamp, TokensLocked()); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inconsistent usage of require + revert everywhere, you should pick one and use that only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we don't need to stay consistent with require vs revert. In some cases, it is more readable to have it in require, while on the others, if () revert makes a little more sense.
| PUFFER.safeTransferFrom(msg.sender, address(this), amount); | ||
| } | ||
|
|
||
| LockInfo memory lockInfo = lockInfos[msg.sender]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to copy this to memory, you can make it a storage variable and also use this variable for the assignment on line 255
| _burn(user, vlPUFFERAmount); | ||
|
|
||
| // Send the rest of the PUFFER tokens to the user | ||
| PUFFER.safeTransfer(user, pufferAmount - kickerFee); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if user can't recover ERC20
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user was the one who initially locked ERC20 tokens. This makes me think, if a user can use 7702 to revert this transaction, making it impossible to get kicked. I'll research this topic a little more, before we finalize the contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see how usign 7702 user could prevent native token from being received. However, this is a ERC20 transfer. Unless our Puffer token tried to call an onTokensReceived hook like ERC777, I don't think user can make the kick tx revert. The only case I can think regarding Tim's comment is the the user is a smart contract without a function to recover ERC20. And about that case, we should just inform about the kick flow so if a contract wants to use this protocol, they should implement a way to recover ERC20
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wrote a test to verify that.
src/vlPUFFER.sol
Outdated
| roundedUnlockTime = uint256(block.timestamp) + (multiplier * _LOCK_TIME_MULTIPLIER); | ||
| } | ||
|
|
||
| function _createLock(uint256 amount, uint256 unlockTime) internal onlyValidLockDuration(unlockTime) whenNotPaused { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getting multiplier as param directly instead of unlock time would make it more readable since there would be no uncertainty or mental calculations
| uint256 internal constant _KICKER_FEE_DENOMINATOR = 10_000; | ||
| // 100% in basis points | ||
| // forgefmt: disable-next-line | ||
| uint256 internal constant _KICKER_FEE_DENOMINATOR = 100_00; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually we don't even need this as a constant. the term BPS already implies 10,000
ksatyarth2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All LGTM now! Just a suggestion to add fuzz tests considering our last audit
|
@tdenisenko Pls review |
No description provided.