Skip to content
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

feat: Add StableSurge hook example with tests. #934

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

johngrantuk
Copy link
Member

Description

Add StableSurge hook example based of ZenDragons initial doc.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Dependency changes
  • Code refactor / cleanup
  • Optimization: [ ] gas / [ ] bytecode
  • Documentation or wording changes
  • Other - Hook example

Checklist:

  • The diff is legible and has no extraneous changes
  • Complex code has been commented, including external interfaces
  • Tests have 100% code coverage
  • The base branch is either main, or there's a description of how to merge

Issue Resolution

@johngrantuk johngrantuk self-assigned this Aug 29, 2024
Copy link
Collaborator

@EndymionJkb EndymionJkb left a 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
Copy link
Collaborator

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.

Copy link
Member Author

@johngrantuk johngrantuk Sep 13, 2024

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.)

pkg/pool-hooks/contracts/StableSurgeHookExample.sol Outdated Show resolved Hide resolved
Comment on lines 76 to 77
emit ThresholdChanged(address(this), threshold);
emit SurgeCoefficientChanged(address(this), surgeCoefficient);
Copy link
Collaborator

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.

Suggested change
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);
}

Copy link
Collaborator

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.

Copy link
Member Author

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.

Copy link
Member Author

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

pkg/pool-hooks/contracts/StableSurgeHookExample.sol Outdated Show resolved Hide resolved
pkg/pool-hooks/contracts/StableSurgeHookExample.sol Outdated Show resolved Hide resolved
uint256 amountInBelowThreshold = 1e18;
uint256 amountInAboveThreshold = 30e18;
// Return from GivenIn swap, input=amountInAboveThreshold, minus fee
uint256 amountOutAboveThreshold = 29.850106042102014697e18;
Copy link
Collaborator

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.

Copy link
Collaborator

@EndymionJkb EndymionJkb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// @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();
Copy link
Collaborator

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Defines the range in which surging will not occur
// Defines the top of the "constant fee" range, below which surging will not occur.

Comment on lines +180 to +184
* 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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,
Copy link
Collaborator

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.

Comment on lines +103 to +104
// Initially set the pool threshold and surge coefficient to
// defaults (can be set by pool swapFeeManager in future).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint64 SWAP_FEE_PERCENTAGE = 0.0004e18;
uint64 SWAP_FEE_PERCENTAGE = 0.04e16;

Percentages (< 100) are always e16

Comment on lines +258 to +259
uint256 surgeCoefficientCheck = (_vault.getStaticSwapFeePercentage(pool) * newSurgeCoefficient) /
(FixedPoint.ONE / _vault.getPoolTokens(pool).length);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants