-
Notifications
You must be signed in to change notification settings - Fork 17
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
Add StableSurge hook example with tests #934
base: main
Are you sure you want to change the base?
Conversation
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.
See questions in the comments; maybe we can sync and talk about the intent and how this should work.
// Only pools from a specific factory are able to register and use this hook. | ||
address private immutable _allowedFactory; | ||
// Defines the range in which surging will not occur; meaning surging will occur when: | ||
// B(i-after swap) / (sum(B(n-after swap))) > 1/n + gamma |
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'm not sure what all the variables mean: i, n, B(), etc. Range of what? Maybe give some more details / a numerical example.
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.
Tried to clarify this a bit more in a more detailed comment. Let me know if this works?
(Also see the linked Notion in the description, not sure how much of that is practical to include in the contract so would be great to get guidance there.)
emit ThresholdChanged(address(this), threshold); | ||
emit SurgeCoefficientChanged(address(this), surgeCoefficient); |
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 we typically do here is encapsulate the event with the set function.
emit ThresholdChanged(address(this), threshold); | |
emit SurgeCoefficientChanged(address(this), surgeCoefficient); | |
_setThreshold(threshold); | |
_setSurgeCoefficient(surgeCoefficient); |
Then, for both the threshold and the surgeCoefficient:
function setThreshold(uint64 newThreshold) external onlyOwner {
_setThreshold(newThreshold);
}
function _setThreshold(uint64 newThreshold) private {
// This should be validated; e.g. <= FixedPoint.ONE or some max value?
_threshold = newThreshold;
emit ThresholdChanged(address(this), newThreshold);
}
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.
How is surge coefficient validated? (i.e., what is the range)? You can't go over 100% on a dynamic fee, or the Vault will revert.
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.
Will check with Zen on best way to validate.
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.
From Zen:
- I don’t think the threshold should matter so much as long as it’s less than 1 - 1/n
- surge coefficient: baseFee * 𝜇 / (1/n) < 100 would be the test then
uint256 amountInBelowThreshold = 1e18; | ||
uint256 amountInAboveThreshold = 30e18; | ||
// Return from GivenIn swap, input=amountInAboveThreshold, minus fee | ||
uint256 amountOutAboveThreshold = 29.850106042102014697e18; |
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.
These would be calculated in the code based on the constants above, but we can look at the tests once the main contract is done.
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
Co-authored-by: EndymionJkb <[email protected]>
… be set by pool swapFeeManager.
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.
See this Notion doc for my thoughts here:
https://www.notion.so/Stable-Surge-algorithm-113e395e2281801ba9f1d452e3a9be29
/// @notice The sender does not have permission to call a function. | ||
error SenderNotAllowed(); | ||
/// @notice Thrown when attempting to set the threshold percentage to an invalid value. | ||
error ThresholdPercentageNotAllowed(); |
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.
Consider InvalidThresholdPercentage
and InvalidSurgeCoefficient
. Usually "NotAllowed" is related to permissions, not numerical values being out-of-range.
mapping(address pool => uint256 surgeCoefficient) public poolSurgeCoefficient; | ||
uint256 public constant DEFAULT_SURGECOEFFICIENT = 50e18; | ||
// A threshold of 0.1 for a 2 token pool means surging occurs if any token reaches 60% of the total of balances. | ||
uint256 public constant DEFAULT_THRESHOLD = 0.1e18; |
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.
uint256 public constant DEFAULT_THRESHOLD = 0.1e18; | |
uint256 public constant DEFAULT_THRESHOLD = 10e16; // 10% |
We express percentages as 1-99e16.
mapping(address pool => uint256 threshold) public poolThresholdPercentage; | ||
// An amplification coefficient to amplify the degree to which a fee increases after the threshold is met. | ||
mapping(address pool => uint256 surgeCoefficient) public poolSurgeCoefficient; | ||
uint256 public constant DEFAULT_SURGECOEFFICIENT = 50e18; |
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.
uint256 public constant DEFAULT_SURGECOEFFICIENT = 50e18; | |
uint256 public constant DEFAULT_SURGE_COEFFICIENT = 50e18; |
Would separate all the words. Also, what is the range here? If it's a percentage, should also be e16 (but maybe it isn't?) You're comparing it to 100e18 later, so for consistency I'd say this should just be 50e16 here, and compared to FixedPoint.ONE later.
using FixedPoint for uint256; | ||
// Only pools from a specific factory are able to register and use this hook. | ||
address private immutable _allowedFactory; | ||
// Defines the range in which surging will not occur |
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.
// Defines the range in which surging will not occur | |
// Defines the top of the "constant fee" range, below which surging will not occur. |
* Defines the range in which surging will not occur. | ||
* @dev An expected value for threshold in a 2 token (n=2) would be 0.1. | ||
* This would mean surging would occur if any token reaches 60% of the total of balances. | ||
* @param numberOfAssets Number of assets in the pool. | ||
* @param thresholdPercentage Thershold percentage value. |
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.
* Defines the range in which surging will not occur. | |
* @dev An expected value for threshold in a 2 token (n=2) would be 0.1. | |
* This would mean surging would occur if any token reaches 60% of the total of balances. | |
* @param numberOfAssets Number of assets in the pool. | |
* @param thresholdPercentage Thershold percentage value. | |
* @notice Defines the top of the "constant fee" range, below which surging will not occur. | |
* @dev An expected value for the threshold in a two-token pool (n=2) would be 0.1. | |
* This would mean surging would occur if any token balance reaches 60% of the total pool value. | |
* @param numberOfAssets Number of assets in the pool | |
* @param thresholdPercentage Threshold percentage value |
function onRegister( | ||
address factory, | ||
address pool, | ||
TokenConfig[] memory, |
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 doesn't use rates anywhere when calculating values. Does this only work with STANDARD tokens then? If so, we should also check that here. Revert if there's a WITH_RATE token. (Or handle rates, if that's a valid case... but then you need to worry about rounding, etc.)
The factory (presumably our standard StableFactory) already restricts it to stable pools.
// Initially set the pool threshold and surge coefficient to | ||
// defaults (can be set by pool swapFeeManager in future). |
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.
// Initially set the pool threshold and surge coefficient to | |
// defaults (can be set by pool swapFeeManager in future). | |
// Initially set the pool threshold and surge coefficient to default values. These can be overridden by the | |
// pool's swapFeeManager in the future. |
|
||
StablePoolFactory internal stablePoolFactory; | ||
|
||
uint64 SWAP_FEE_PERCENTAGE = 0.0004e18; |
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.
uint64 SWAP_FEE_PERCENTAGE = 0.0004e18; | |
uint64 SWAP_FEE_PERCENTAGE = 0.04e16; |
Percentages (< 100) are always e16
uint256 surgeCoefficientCheck = (_vault.getStaticSwapFeePercentage(pool) * newSurgeCoefficient) / | ||
(FixedPoint.ONE / _vault.getPoolTokens(pool).length); |
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.
uint256 surgeCoefficientCheck = (_vault.getStaticSwapFeePercentage(pool) * newSurgeCoefficient) / | |
(FixedPoint.ONE / _vault.getPoolTokens(pool).length); | |
uint256 surgeCoefficientCheck = (_vault.getStaticSwapFeePercentage(pool) * newSurgeCoefficient).mulDown( | |
_vault.getPoolTokens(pool).length | |
); |
Have suggestions for refactoring this more, but the above can be simplified to this to start with.
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.
Another problem here is you're reading the static fee from the Vault (here and in the dynamic fee hook), but it can be changed, invalidating the surge coefficient (e.g., could change to something that would fail).
So you'd have to fix the static fee percentage on registration, and store it. There could be a permissioned "update static fee percentage" that could update it from the Vault to allow it to recognize changes, but it would then have to check that the coefficient is correct (or reset it to 1 or something known valid).
Seems complex and error-prone.
Description
Add StableSurge hook example based of ZenDragons initial doc.
Type of change
Checklist:
main
, or there's a description of how to mergeIssue Resolution