-
Notifications
You must be signed in to change notification settings - Fork 504
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
OZ-L10, SB-L53, SB-L54, SB-M78; Check code length on Notifications, Unsubscribe Minimum Gas Limit #318
Conversation
src/base/Notifier.sol
Outdated
// require that the remaining gas is sufficient to notify the subscriber | ||
// otherwise, users can select a gas limit where .notifyUnsubscribe hits OutOfGas yet the transaction/unsubscription | ||
// can still succeed | ||
// to account for EIP-150, condition could be 64 * gasleft() / 63 <= subscriberGasLimit | ||
if ((64 * gasleft() / 63) < subscriberGasLimit) GasLimitTooLow.selector.revertWith(); | ||
try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config, data) {} catch {} |
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.
need extra eyes on this (and the natspec)
feels weirdly unintuitive that users may need to specify a very high gas limit on .unsubscribe()
calls
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 it means that all users will have to send in gasLimit > subscriberGasLimit
, even if their particular subsciber only needs like 10k in gas. Which sucks really... but I can't see any other way around it?
I think it also means that it will be really hard to run estimateGas because of this require statement.
src/base/Notifier.sol
Outdated
// otherwise, users can select a gas limit where .notifyUnsubscribe hits OutOfGas yet the transaction/unsubscription | ||
// can still succeed | ||
// to account for EIP-150, condition could be 64 * gasleft() / 63 <= subscriberGasLimit | ||
if ((64 * gasleft() / 63) < subscriberGasLimit) GasLimitTooLow.selector.revertWith(); |
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.
in core we dont bother with the *64/63. Theres just a comment about it
https://github.com/Uniswap/v4-core/blob/4dc48bbd89f4faf1e9493cd1563908d79aa10b0d/src/ProtocolFees.sol#L76
thoughts?
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.
agree
Co-authored-by: Alice <[email protected]>
@@ -10,6 +10,9 @@ interface ISubscriber { | |||
/// @param config details about the position | |||
/// @param data additional data passed in by the caller | |||
function notifySubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; | |||
/// @notice Called when a position unsubscribes from the subscriber | |||
/// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment) | |||
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft() |
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.
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft() | |
/// @dev Because of EIP-150, solidity may only allocate 63/64 of unsubscribeGasLimit |
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 actually think its clearer to leave it as
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft()
because this only happens when gasLeft == unsubscriberGasLimit
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 just mean that subscribers should know that they actually cannot take more than 63/64 of unsubscribeGasLimit because thats the lower bound of gas they will receive
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 agree that is the lower bound !
src/base/Notifier.sol
Outdated
@@ -65,13 +66,17 @@ abstract contract Notifier is INotifier { | |||
function _unsubscribe(uint256 tokenId, PositionConfig calldata config) internal { | |||
_positionConfigs(tokenId).setUnsubscribe(); | |||
ISubscriber _subscriber = subscriber[tokenId]; | |||
if (_subscriber == NO_SUBSCRIBER) NotSubscribed.selector.revertWith(); |
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 that this check makes sense, surprised it wasnt here before
doesnt one of these issues say to add it? i cant find it
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 think it may make more sense to check _postionConfigs().hasSubscriber? i guess see if its cheaper in the case where they do have a subscriber?
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.
yep looked like .hasSubscriber() was slightly cheaper! refactored it
src/base/Notifier.sol
Outdated
if ((64 * gasleft() / 63) < subscriberGasLimit) GasLimitTooLow.selector.revertWith(); | ||
try _subscriber.notifyUnsubscribe{gas: subscriberGasLimit}(tokenId, config) {} catch {} | ||
// to account for EIP-150, condition could be 64 * gasleft() / 63 <= unsubscribeGasLimit | ||
if ((64 * gasleft() / 63) < unsubscribeGasLimit) GasLimitTooLow.selector.revertWith(); |
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 think we do not need to calculate the (64 * gasLeft / 63) check.
Really this line is just being added to enforce some minimum amount that a user MUST send.
That minimum amount doesn't exactly need to be 63/64ths of the unsubscribeGasLimit. It can just be at least the unsubscribeGasLimit. And we can add a comment that in 1 case (when gasLeft == gasLimit) the call will only forward 63/64ths of the gasLimit... which is fine. Basically we can just keep this in mind when parameterizing the unsubscribeGasLimit
.
We really want to add this line because we do not want the user to ever send a minimal amount of gas such that the unsubscribe block reverts but the rest of the function succeeds
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 that we changed it to document EIP150. I just left some comments in response to Alice's about the natspec!
Looks good!
// a percentage of the block.gaslimit denoted in BPS, used as the gas limit for subscriber calls | ||
// 100 bps is 1%, at 30M gas, the limit is 300K | ||
uint256 private constant BLOCK_LIMIT_BPS = 100; | ||
/// @inheritdoc INotifier |
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.
nit: i think there is an extra line added that can be removed.
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.
this natspec? fwiw i added this to INotifier
/// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe
/// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function
function unsubscribeGasLimit() external view returns (uint256);
src/base/Notifier.sol
Outdated
@@ -65,13 +66,17 @@ abstract contract Notifier is INotifier { | |||
function _unsubscribe(uint256 tokenId, PositionConfig calldata config) internal { | |||
_positionConfigs(tokenId).setUnsubscribe(); | |||
ISubscriber _subscriber = subscriber[tokenId]; | |||
if (_subscriber == NO_SUBSCRIBER) NotSubscribed.selector.revertWith(); |
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 think it may make more sense to check _postionConfigs().hasSubscriber? i guess see if its cheaper in the case where they do have a subscriber?
@@ -10,6 +10,9 @@ interface ISubscriber { | |||
/// @param config details about the position | |||
/// @param data additional data passed in by the caller | |||
function notifySubscribe(uint256 tokenId, PositionConfig memory config, bytes memory data) external; | |||
/// @notice Called when a position unsubscribes from the subscriber | |||
/// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment) | |||
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft() |
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 actually think its clearer to leave it as
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft()
because this only happens when gasLeft == unsubscriberGasLimit
Co-authored-by: Alice <[email protected]>
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.
nice
Related Issue
Which issue does this pull request resolve?
OZ-L10: calls to addresses without bytecode will still hard-revert, even with try/catch
For chains that still support
selfdestruct
, you can subscribe to a valid contract but then lose access to unsubscribing if the contract self-destructsSB L53: Notifications call contracts without code
SB L54: Subscribing multiple times to contracts without code
SB M78: Unsubscribe should have a Minimum Gas Limit
Description of changes
unsubscribe
will not revertsubscribe, transfer, and modifyLiquidity
will revert