-
Notifications
You must be signed in to change notification settings - Fork 4
Feat: protocol fee split #43
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
base: master
Are you sure you want to change the base?
Conversation
contracts/DXswapFeeReceiver.sol
Outdated
| (uint256 reserveIn, uint256 reserveOut) = token0 < token1 ? (reserve0, reserve1) : (reserve1, reserve0); | ||
| if (reserveIn == 0 || reserveOut == 0) return false; | ||
|
|
||
| uint256 priceImpact = amount.mul(10000) / reserveIn; // simplified formula |
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 guess either use SafeMath or not. Let's not mix:)
so either:
amount.mul(10000).div(reserveIn);
or
amount * 10000 / reserveIn
contracts/DXswapFeeReceiver.sol
Outdated
| address public WETH; | ||
| address public ethReceiver; | ||
| address public fallbackReceiver; | ||
| uint32 public maxSwapPriceImpact = 100; // uses default 1% as max allowed price impact for takeProtocolFee 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.
If this is expressed in basis points it can probably be a uint16.
|
|
||
| address public owner; | ||
| IDXswapFactory public factory; | ||
| address public WETH; |
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 can't remember if this is actually intended to be WETH or just the deployment chain's native currency wrapper (WXDAI on GC for example). If the latter applies, I'd suggest a rename to nativeCurrencyWrapper or something like that.
P.S.: deriving from the below logic, it looks like WETH should indeed be called in a more network-neutral way.
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 the framework has been to convert the fees, which start in the form of LP tokens, into the chain's native currency if possible. This has made sense thus far with Ethereum Mainnet, Arbitrum (using ETH as native currency), and Gnosis Chain (using xDai). However, I think generally speaking it's not obvious that the protocol fee should always be converted to native currency. It could depend a lot on which chain. Maybe we should consider making the target to convert the fee to more flexible? But in the context of how DXdao has been using the feeReceiver, then the above comment is correct that the intent is to be the chain's native currency and not always WETH.
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.
Thing is, this contract only really works with the target chain's native asset and its wrapper as is (as you recognize). If we want to make this more flexible I think we have two options:
- If we want to keep the current logic unchanged, another contract would be needed, dealing with a standard ERC20 token for example.
- Even though this option would alter the logic a little bit (in my opinion in very acceptable or even desirable ways) I'd avoid calling
withdrawon the wrapper when executing_takeETHorToken, have the contract send wrapped native currency to the recipient and convert the above variable to a standard ERC20 named for exampletargetTokenor something. This is desirable to simplify the logic, reducing gas consumption and attack surface. As is, for each harvested LP token that is convertible to the native currency (or, actually, its wrapper) we also perform the withdraw. It might be more sensible to offload the withdraw to the recipient for it to be performed only when actually needed.
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 the approach of the second option makes sense. The first implementation deployment of the feeReceiver was on mainnet and the recipient was the bonding curve, which needs to be sent ETH in order for it to work. That fact probably influenced our initial implementation. However, I don't think the new implementation needs to send to the bonding curve, as the bonding curve is now paused and in all likelihood will be sunset.
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 agree that the second option makes sense!
Ok, so if we decide not to swap to ETH at all then the _swapTokensForETH function can be removed as well as withdraw + safeTransferETH. Instead the new argument will be added like targetToken. By default feeReceiver will try to swap to targetToken which is ERC20 and if it's not possible send LP to the fallbackReceiver.
Am I right or misunderstood sth?
contracts/DXswapFeeReceiver.sol
Outdated
| address _owner, address _factory, address _WETH, address _ethReceiver, address _fallbackReceiver | ||
| address _owner, | ||
| address _factory, | ||
| address _WETH, |
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 above applies, this should be changed too.
| import './interfaces/IDXswapFactory.sol'; | ||
| import './interfaces/IDXswapPair.sol'; | ||
| import './interfaces/IWETH.sol'; | ||
| import './libraries/TransferHelper.sol'; |
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 honestly upgrade to Solidity v0.8.13 or later if available and leverage both:
- The fact that with Sol >=v0.8.0, safe math is not really required anymore since under/overflow checks are automatically performed.
- The Yul optimizer is production ready and I'd use it to shave off some gas consumption.
contracts/DXswapFeeReceiver.sol
Outdated
| emit TakeProtocolFee(msg.sender, ethReceiver, pairs.length); | ||
| } | ||
|
|
||
| // called by the owner to set maximum swap price impact allowed for single token-weth swap (within 0-100% range) |
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.
Should maybe clarify what number correspond to what percentages
contracts/DXswapFeeReceiver.sol
Outdated
| (uint256 reserveIn, uint256 reserveOut) = token0 < token1 ? (reserve0, reserve1) : (reserve1, reserve0); | ||
| if (reserveIn == 0 || reserveOut == 0) return false; | ||
|
|
||
| uint256 priceImpact = amount.mul(10000) / reserveIn; // simplified formula |
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 this is intentionally a simplified formula. Do we think it's good enough?
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 done quite a lot of calculations (if you're interested in details I can share the sheet) and imo it's good enough just as an extra protection step. Ofc it is possible to implement more complex formula, but that could cost more. I'll check it out and compare gas usage.
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 like to see more if possible.
|
|
||
| address public owner; | ||
| IDXswapFactory public factory; | ||
| address public WETH; |
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.
Thing is, this contract only really works with the target chain's native asset and its wrapper as is (as you recognize). If we want to make this more flexible I think we have two options:
- If we want to keep the current logic unchanged, another contract would be needed, dealing with a standard ERC20 token for example.
- Even though this option would alter the logic a little bit (in my opinion in very acceptable or even desirable ways) I'd avoid calling
withdrawon the wrapper when executing_takeETHorToken, have the contract send wrapped native currency to the recipient and convert the above variable to a standard ERC20 named for exampletargetTokenor something. This is desirable to simplify the logic, reducing gas consumption and attack surface. As is, for each harvested LP token that is convertible to the native currency (or, actually, its wrapper) we also perform the withdraw. It might be more sensible to offload the withdraw to the recipient for it to be performed only when actually needed.
contracts/DXswapFeeReceiver.sol
Outdated
|
|
||
| mapping(address => ExternalFeeReceiver) public externalFeeReceivers; | ||
|
|
||
| event TakeProtocolFee(address indexed sender, address indexed to, uint256 NumberOfPairs); |
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.
Change NumberOfPairs to camel case.
| function() external payable {} | ||
|
|
||
| // called by the owner to set the new owner | ||
| function transferOwnership(address newOwner) 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.
I'm actually becoming a fan of the acceptOwnership method of doing ownership transfers. In case ownership of a contract is delicate, better to implement this pattern with increased security.
contracts/DXswapFeeReceiver.sol
Outdated
| function isContract(address addr) internal returns (bool) { | ||
| uint size; | ||
| assembly { size := extcodesize(addr) } | ||
| function isContract(address addr) internal view returns (bool) { |
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 must be replaced by a call to the factory. Increases gas consumption a little bit, but since it's only used to check if a pair exists, in the process we also have guarantees about the validity of a pair (and we avoid surprises like the hack we've had).
contracts/DXswapFeeReceiver.sol
Outdated
| (uint256 reserveIn, uint256 reserveOut) = token0 < token1 ? (reserve0, reserve1) : (reserve1, reserve0); | ||
| if (reserveIn == 0 || reserveOut == 0) return false; | ||
|
|
||
| uint256 priceImpact = amount.mul(10000) / reserveIn; // simplified formula |
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 like to see more if possible.
contracts/DXswapFeeReceiver.sol
Outdated
| // called by the owner to set maximum swap price impact allowed for single token-weth swap (within 0-100% range) | ||
| function setMaxSwapPriceImpact(uint32 _maxSwapPriceImpact) external { | ||
| require(msg.sender == owner, 'DXswapFeeReceiver: CALLER_NOT_OWNER'); | ||
| require(_maxSwapPriceImpact > 0 && _maxSwapPriceImpact < 10000, 'DXswapFeeReceiver: FORBIDDEN_PRICE_IMPACT'); |
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 first check _maxSwapPriceImpact > 0 is not needed since we're dealing with unsigned integers.
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.
Indeed it's not needed for unsigned integers, but this check is done to not to set priceImpact to zero.
contracts/DXswapFeeReceiver.sol
Outdated
| // called by the owner to set fee percentage to external receiver | ||
| function setFeePercentageToExternalReceiver(address _pair, uint32 _feePercentageToExternalReceiver) external { | ||
| require(msg.sender == owner, 'DXswapFeeReceiver: CALLER_NOT_OWNER'); | ||
| IDXswapPair swapPair = IDXswapPair(_pair); |
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.
Please let's not assume the input is a Swapr pair, not even if only the owner can perform the call here.
contracts/DXswapFeeReceiver.sol
Outdated
| uint256 feeReceiverBalance = swapPair.balanceOf(address(this)); | ||
| if (feeReceiverBalance > 0) { | ||
| // withdraw accumulated fees before updating the split percentage | ||
| address token0 = swapPair.token0(); |
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 common logic could probably be extracted in a function to avoid duplicated code.
contracts/DXswapFeeReceiver.sol
Outdated
| if (amount1 > 0) _takeTokenOrETH(address(swapPair), token1, amount1); | ||
| emit TakeProtocolFee(msg.sender, ethReceiver, 1); | ||
| } | ||
| require(swapPair.balanceOf(address(this)) == 0, 'DXswapFeeReceiver: TOKENS_NOT_BURNED'); |
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 might fail. When performing burn some fee tokens might be minted depending on the pair's activity.
contracts/DXswapFeeReceiver.sol
Outdated
|
|
||
| // fee percentage check | ||
| require( | ||
| _feePercentageToExternalReceiver >= 0 && _feePercentageToExternalReceiver <= 5000, |
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.
_feePercentageToExternalReceiver >= 0 can be removed, dealing with unsigned integers.
…by calling factory
Summary
Closes #39
The goal is to upgrade FeeReceiver contract to solve two issues:
New features