-
Notifications
You must be signed in to change notification settings - Fork 0
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
More efficient dynamic fee setter #1
base: master
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.
Could we move all these files that have been renamed without changes back to their original locations so as not to clutter the PR?
@@ -18,7 +18,18 @@ export default { | |||
}, | |||
}, | |||
solidity: { | |||
compilers: hardhatBaseConfig.compilers, | |||
// compilers: hardhatBaseConfig.compilers, |
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 is probably something we should leave as was to avoid introducing issues to the monorepo
|
||
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/Ownable.sol"; | ||
|
||
contract CustomFeeAuthorizer is Ownable { |
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 worry this might conflict with Balancer's own access control mechanisms. I think it would make more sense instead to introduce the notion of address public solver
; solver
must be set on pool creation and can't be edited; solver
can edit isCustomFeeSetter
and call setSwapFeePercentage
function setSwapFeePercentage(uint256 swapFeePercentage) public virtual override whenNotPaused { | ||
// here msg.sender is used as this function directlly be called by the admin | ||
// not via any other contract | ||
if(isCustomFeeEnabled && solver == 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.
We don't want governance to be able to set fees in this pool, so this needs to be something like
if (isCustomFeeEnabled) {
require(solver == msg.sender,'CALLER_IS_NOT_SOLVER');
_setSwapFeePercentage(swapFeePercentage);
} else {
super.setSwapFeePercentage(swapFeePercentage);
}
isCustomFeeEnabled = true; | ||
} | ||
|
||
function _setSolverAddress(address _solver) internal { |
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.
Shouldn't this be public, onlySolver?
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.
if we can mark isCustomFeeEnabled
as immutable then we can call this function in the constructor based on the value of isCustomFeeEnabled
.
But if not then initially 0 address will be marked as solver so we cannot use onlySolver
here. But we can have a logic where initially owner() can call this function and after that onlySolver
can call that
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.
@markusbkoch who should set the solver? Governance, or is it fixed at pool creation time? Can the existing solver pass the torch to another address?
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 don't see any references to _setSolverAddress
, so this needs to be worked out anyways.
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.
@markusbkoch who should set the solver? Governance, or is it fixed at pool creation time? Can the existing solver pass the torch to another address?
Governance should not be able to set it. Fixed would be good enough, solver being able to pass it on would be a nice to have
emit feeSetterRemoved(_toRemove); | ||
} | ||
|
||
function enableCustomFee() onlySolver() internal { |
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 needs to be public?
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.
If we want a toggle function for isCustomFeeEnabled
then we can mark this function as public.
But if we can mark isCustomFeeEnabled
as immutable then we will have to pass the value of isCustomFeeEnabled
from WeightedPool constructor so we can remove this function.
event feeSetterRemoved(address indexed feeSetter); | ||
|
||
mapping(address => bool) private isCustomFeeSetter; | ||
bool public isCustomFeeEnabled = false ; |
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.
Safe to say solvers will always want this to be true. If we could we make this immutable, does the code become more efficient?
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.
If we want to make isCustomFeeEnabled
as immutable
then we will have to initialize this in the constructor. But if we pass 1 more argument in the constructor of WeightedPool
it gives stack to deep error.
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.
First pass done!
I've left some minor comments, and I have some questions around how the solver is set / how governance would work. Also, the pool layers make it a bit difficult to reason just by seeing the diff, but is this feature intended to be used just for weighted pools, or for every pool type?
On top of that, it'd be good to see tests to cover the new functionality
function getSwapFeePercentage(bytes memory userData, | ||
OperationType _operation) public view virtual returns (uint256) { | ||
// this code is just to avoid un-used variable warning. | ||
// this will have no impact on the execution | ||
// will remove this code in future | ||
if(_operation == OperationType.SWAP){ | ||
userData = hex"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.
This can be solved by removing the variable name from the declaration.
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.
Anyways I'd consider removing this from BasePool
if this is a feature that will only affect WeightedPool
.
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.
Anyways I'd consider removing this from
BasePool
if this is a feature that will only affectWeightedPool
.
We'd like this feature in other pools too
@@ -36,6 +36,8 @@ abstract contract BaseMinimalSwapInfoPool is IMinimalSwapInfoPool, BasePool { | |||
) public override onlyVault(request.poolId) returns (uint256) { | |||
_beforeSwapJoinExit(); | |||
|
|||
emit SwapFeePercentageChanged(getSwapFeePercentage(request.userData, OperationType.SWAP)); |
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're doing all the work here to get the right fee and emit the event, and doing it all over again inside _substractSwapFeeAmount
. I'd save the value and pass it as an argument.
// _doJoin performs actions specific to type of join | ||
// but it's a view function so can not emit event |
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'd consider making _doJoin
non-view if needed. I'll give this some more thought.
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.
Have you thought about this? Should we make it a non-view function?
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.
After some more thought, I think it's fine as it is.
WeightedPoolUserData.JoinKind kind = userData.joinKind(); | ||
if(kind == WeightedPoolUserData.JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT || | ||
kind == WeightedPoolUserData.JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT){ | ||
emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.JOIN)); |
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.
Same as above; we're getting the fee % inside _doJoin
as well. Maybe it's better to just save it and pass it along.
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.
If I create an extra variable it gives stack too deep error.
WeightedPoolUserData.ExitKind kind = userData.exitKind(); | ||
if(kind == WeightedPoolUserData.ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT || | ||
kind == WeightedPoolUserData.ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT){ | ||
emit SwapFeePercentageChanged(getSwapFeePercentage(userData, OperationType.EXIT)); |
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.
Same as above (store and pass)
isCustomFeeEnabled = true; | ||
} | ||
|
||
function _setSolverAddress(address _solver) internal { |
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.
@markusbkoch who should set the solver? Governance, or is it fixed at pool creation time? Can the existing solver pass the torch to another address?
isCustomFeeEnabled = true; | ||
} | ||
|
||
function _setSolverAddress(address _solver) internal { |
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 don't see any references to _setSolverAddress
, so this needs to be worked out anyways.
function getSwapFeePercentage(bytes memory userData, | ||
OperationType _operation) public view virtual override returns (uint256 _fee) { |
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.
Alright, this doesn't seem to be a bad pattern. Assuming isCustomFeeEnabled
is always true, then only those who can register the custom fee go through a different codepath when compared to the actual weighted pool.
require(solver == msg.sender,'CALLER_IS_NOT_SOLVER'); | ||
_setSwapFeePercentage(swapFeePercentage); | ||
} else { | ||
super.setSwapFeePercentage(swapFeePercentage); |
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.
If isCustomFeeEnabled
is going to be immutable / true, that would break the capability of Governance of setting fees to any pools created from here.
Should Governance always be capable of setting the fee anyways? I'd consider using something like authenticateFor
.
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, governance should not be able to set the fess. The solver has certain expectations regarding this pool's fee, governance being able to set the fee could potentially rug the solver
@@ -45,6 +45,9 @@ interface IBasePool is IPoolSwapStructs { | |||
* Contracts implementing this function should check that the caller is indeed the Vault before performing any | |||
* state-changing operations, such as minting pool shares. | |||
*/ | |||
// enum for transaction type | |||
enum OperationType { JOIN, EXIT, SWAP } |
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.
@markusbkoch do we want this feature to apply to weighted pools, or to every pool type?
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.
every pool type
Hey @jubeira, |
While running the test cases I was getting some some issues. Most of them is resolved. But 1 is remaining. |
@srv-smn I've addressed most of the comments on top of this branch and opened a PR here: balancer#2558. Pending:
|
Overrides getSwapFeePercentage so as to be able to pass userdata as an argument and dynamically set fees without having to write to storage before/after every swap.